Where are the monads, CPS and these functional buzzwords in Nix?

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