This is mostly a curiosity of mine.
I learned a bit of Haskell by reading Yet Another Haskell Tutorial.
I had a mind blowing moment with the concepts of Continuation Passing Style and Monads.
I am still trying to understand them, to be honest.
With this background I ask for each and every person interested in some interaction:
- Have you ever seen something related to {continuation passing style, monads} written in the Nix language?
- Do you believe these functional design patterns can be useful in Nixpkgs?
1 Like
Monads as an abstraction only make sense in a statically-typed language. It can make sense to use functions that happen to be monadic binds for a specific type in a dynamically-typed language like Nix (builtins.concatMap
is bind for lists, of course/for example), but IMO the concept of abstracting over multiple monads with a uniform API or syntax is unlikely to be helpful in this version of the Nix language.
I haven’t seen a need for CPS in any Nix code I’ve worked with, but someone conceivably could have!
2 Likes
I don’t necessarily think monads and continuation passing style wouldn’t be useful in Nix; I think the language just isn’t really expressive enough for them. The ability to have generic functions that work with arbitrary effects, like State
, Maybe
, and Either
is something I’ve often wanted in non-Haskell languages, including Nix. My favorite example of these sorts of things is the Traversable
class
class Functor t => Traversable t where
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
instance Traversable [] where
traverse _ [] = pure []
traverse f (x:xs) = liftA2 (:) (f x) (traverse f xs)
Which lets you iterate over arbitrary collections using arbitrary effects. Now, in Nix, we’re likely to only really need lists and attrsets, so I don’t really mind needing to use specific traverseList
and traverseAttrs
functions or something; but the important part is being able to create arbitrary monads / applicatives that work with those. And Nix just doesn’t really have the expressiveness to do these things cleanly. You can use a bunch of unwieldy workarounds to get it done, but you end up with stuff like explicit dictionary passing and church encoding.
rec {
# A church encoded `Either a b` is a function of type
# `(a -> r) -> (b -> r) -> r`,
# which simply calls the left or right higher order
# function argument depending on if it's a `Left` or a `Right`
mkLeft = x: left: right: left x;
mkRight = x: left: right: right x;
# An `Applicative` instance can be represented
# with an explicit dictionary.
eitherApplicative = {
pure = mkRight;
liftA2 = f: x: y: left: right:
x left (xRight: y left (yRight: right (f xRight yRight)));
};
traverseList = applicativeDict: f: xs:
builtins.foldl'
(acc: x: applicativeDict.liftA2 (ys: y: ys ++ [y]) acc (f x))
(applicativeDict.pure [])
xs;
fmap = applicativeDict: f: x:
applicativeDict.liftA2
(_: y: f y)
(applicativeDict.pure null)
x;
# This example will return `Left x`, where `x` is the first even number
# Or it will return `Right (sum xs)` if there are no evens.
example = xs: let
isEven = x: builtins.bitAnd 1 x == 0;
sum = builtins.foldl' (acc: x: acc + x) 0;
odds = traverseList
eitherApplicative
(x: if isEven x then mkLeft x else mkRight x)
xs;
in fmap eitherApplicative sum odds;
}
nix-repl> example [1 3 5] (e: "There's an even ${toString e}") (s: "The sum is ${toString s}")
"The sum is 9"
nix-repl> example [1 2 3 5] (e: "There's an even ${toString e}") (s: "The sum is ${toString s}")
"There's an even 2"
1 Like