Today I am a professor in computer science and still draw on it for examples in my advanced functional programming course. Just last week we did the loeb function, as an example of interesting use of Functor.
Loeb function: http://blog.sigfpe.com/2006/11/from-l-theorem-to-spreadsheet...
In addition to the free monad presented in this post, there is a variant, called the "freer" monad, based on the "bind" operation instead of the "join" operation:
data Freer f a where
Pure :: a -> Freer f a
Bind :: f a -> (a -> Freer f b) -> Freer f b
I believe this definition originates from the following paper by Oleg Kiselyov and Hiromi Ishii: https://okmij.org/ftp/Haskell/extensible/more.pdfWhen thinking of monads as giving the semantics of some computational strategy, it's easier to define them in terms of "bind" instead of "join." This way of defining monads is sometimes called a "Kleisli triple" because it is better suggestive of "Kleisli arrows," or functions of the signature `a -> m b`. The "bind" operation defines how to compose a monadic computation with its continuation, and from this perspective, the "freer" monad resembles an abstract syntax tree.
Originally, Eugenio Moggi proposed monads as a technique for specifying the denotational semantics of programming languages. All Java programs "really" happen in the IO + Either monads, because all Java programs may perform IO and throw exceptions. To my understanding, free monads are the monad that OCaml 5 runs in, because they give the semantics for effect handlers (or resumable exceptions).
A classic that everyone should read: http://blog.sigfpe.com/2006/08/you-could-have-invented-monad...
I attended a talk of his at Papers We Love at Strange Loop in 2018, I didn't really read the description and I was vaguely expecting something Haskell related, and instead got this: https://www.youtube.com/watch?v=766obijdpuU
I could barely understand it, but was impressed by what I could grasp. Dan Piponi's range is amazing, dude is brilliant
If you have some function fetchResponse that returns a Promise<Response>, and a function processJSON that takes a Response as an argument, you cannot compose them the usual way, as `processJSON(fetchResponse(x))`. Instead, you need to do `fetchReponse(x).then(processJSON)`, and the entire expression has to return another Promise. Ditto for functions that return an Optional.
All data types that implement this design pattern have the structure of a monad. A monad consists of a generic type that is "covariant" in its type parameter (such as Promise or Optional), a way to embed a singular value into the data type, and a "then" method to compose the data type with a callback. Lists also implement the monad design pattern, and the "then" method for lists is flatmap. A monad basically lets you compose functions with a type signature that looks like `A -> T<B>`.
Furthermore, each of these data types (Promises, Optionals, Lists) can be viewed as the output of some computation. Promises are produced whenever a function performs asynchronous IO, Optionals are produced when computations may return some "null" value, and Lists are returned if an algorithm may produce multiple solutions.
Just like Promises have async/await syntactic sugar, similar syntactic sugar can be devised for other "monadic" types. For Optionals, the equivalent of async/await is null propagation (some languages have a `?` operator for this). For Lists, the equivalent of async/await is list comprehension, which "picks" each element from the list to build up a new list. Async/await, null propagation, and list comprehensions all have the same underlying structure, called a "monad."
A "free monad" is a monad that does not implement any specific computation, but instead builds up an abstract syntax tree that needs to be interpreted. Free monads are useful because other monad instances can be implemented in terms of the free monad.
See also https://jerf.org/iri/post/2958/ .
People say things like this all the time, and I think it's a vacuous assertion. While there is probably some very narrow view where returning early with an error, throwing an exception, and binding in Either are the same thing, such a view ignores a lot of important context (e.g. all of imperative programming). This is why you have to qualify it as "IO + Either", but that doesn't say anything at all because everything is possible in IO.
Why is most writing about functional programming like this?
Do you understand "flatmap"? Good, that's literally all a monad is: a flatmappable.
Technically it's also an applicative functor, but at the end of the day, that gives us a few trivial things:
- a constructor (i.e., a way to put something inside your monad, exactly how `[1]` constructs a list out of a natural number)
- map (everyone understands this bc we use them with lists constantly)
- ap, which is basically just "map for things with more than one parameter"
Monads are easy. But when you tell someone "well it's a box and you can unwrap it and modify things with a function that also returns a box, and you unwrap that box take the thing out and put it inside the original box—
No. It is a flatmappable. That's it. Can you flatmap a list? Good. Then you already can use the entirety of monad-specific properties.
When you start talking about Maybe, Either, etc. then you've moved from explaining monads to explaining something else.
It's like saying "classes are easy" and then someone says "yeah well what about InterfaceOrienterMethodContainerArrangeableFilterableClass::filter" that's not a class! That's one method in a specific class. Not knowing it doesn't mean you don't understand classes. It just means you don't have the standard library memorized!
So then when you look at List, Maybe, Either, et al. it's interesting to see how their conforming to the laws "unpacks" differently with respect to what they each do differently (what's happening to the data in your program), but the laws are just the same.
The reason this was an aha moment for me is that I struggled with wanting to understand a monad as another kind of thing — "I understand what a function is, I understand what objects and primitive values are, but I don't get that List and Maybe and Either are the same kind of thing, they seem like totally different things!"
Outside of FP however, this seems really stupid. We're used to operations that happen in the order you wrote them in and function applications that just so happen to also print things to the screen or send bits across the network. If you live in this world, like most people do, then "flatmap" is a good metaphor for Monads because that's basically all they do in an imperative language[1].
Well, that, and async code. JavaScript decided to standardize on a Monad-shaped "thenable" specification for representing asynchronous processes, where most other programming languages would have gone with green threads or some other software-transparent async mechanism. To be clear, it's better than the callback soup you'd normally have[0], but working with bare Thenables is still painful. Just like working with bare Monads - which is why Haskell and JavaScript both have syntax to work around them (await/async, do, etc).
Maybe/Either get talked about because they're the simplest Monads you can make, but it makes Monads sound like a spicy container type.
[0] The FP people call this "continuation-passing style"
[1] To be clear, Monads don't have to be list-shaped and most Monads aren't.
Awesome! Now I understand.
> Technically it's also an applicative functor
Aaaand you've lost me. This is probably why people think monads are difficult. The explanations keep involving these unfamiliar terms and act like we need to already know them to understand monads. You say it's just a flatmappable, but then it's also this other thing that gives you more?
And if-then statements are functorial.
These are very general thought patterns.
Actually "spicy container type" is maybe a better definition of Monad than you may think. There's a weird sort of learning curve for Monads where the initial reaction is "it's just a spicy container type", you learn a bit and get to "it is not just a spicy container type", then eventually you learn a lot more and get to "sure fine, it's just a spicy container type, but I was wrong about what 'container' even means" and then settle back down to "it's a spicy container type, lol".
"It's a spicy container type" and "it's anything that is flatmappable" are two very related simplifications, if "container" is a good word for "a thing that is flatmappable". It's a terrible tautological definition, but it's actually not as bad of a definition as it sounds. (Naming things is hard, especially when you get way out into mathematical abstractions land.)
There are flatmappable things that don't have anything to do with ordering or sequencing. Maybe is a decent example: you only have a current state, you have no idea what the past states were or what order they were in.
Flatmappable things are generally (but not always) non-commutative: if you flatmap A into B you get a different thing than if you flatmap B into A. That can represent sequencing. With a Promise `A.then(() => B)` is different sequence than `B.then(() => A)`. But that's as much "domain specific" to the Promise Monad and what its flatmap operation is (which we commonly call `then` to make it a bit more obvious what its flatmap operation does, it sequences; A then B) than anything fundamental to a Monad. The fundamental part is that it has a flatmap operator (or bind or then or SelectMany or many other language or domain-specific names), not anything to do with what that flatmap operator does (how it is implemented).
1. my explanation of monad is sufficient for people who need to use them
2. your explanation of monad is necessary for people who might want to invent new ones
What I mean by this is that if you want to invent a new monad, you need to make sure your idea conforms to the monad laws. But if you're just going to consume existing monads, you don't need to know this. You only need to know the functions to work with a monad: flatmap (or map + flatten), ap(ply), bind/of/just. Everything else is specific to a given monad. Like an either's toOptional is not monadic. It's just turning Left _ into None and Right an into Some a.
And needing to know these properties "work" is unnecessary, as their very existence in the library is pretty solid evidence that you can use them, haha.
Monads have nothing to do with order (they follow the same ordering as Haskell's normalization guarantees).
> JavaScript decided to standardize on a Monad-shaped "thenable" specification for representing asynchronous processes,
Its impossible for something to be monad shaped. All asynchronous interfaces form a monad whether you decide to follow the Haskell monad type class or decide to do something else. They're all isomorphic and form a monad. Any model of computation forms a monad.
Assembly language quite literally forms a category over the monoid of endo functors.
Jacquard loom programming also forms a category over the monoid of endo functors because all processes that sequence things with state form such a thing, whether you know that or not.
It's like claiming the Indians invented numbers to fit the addition algorithm. Putting the cart before the horse, because all formations of the natural numbers form a natural group/ring with addition and multiplication formed the standard way (they also all form separate groups and rings, that we barely ever use).
This is the kind of explanation that drives me absolutely batshit crazy because it is fundamentally at odds with:
> Do you understand "flatmap"? Good, that's literally all a monad is: a flatmappable.
So, I think I understand flatmap, assuming that this is what you mean:
https://www.w3schools.com/Jsref/jsref_array_flatmap.asp
But this has absolutely nothing to do with "certain things are supposed to happen after other things", and CANNOT POSSIBLY have anything to do with that. Flatmap is a purely functional concept, and in the context of things that are purely functional, nothing ever actually happens. That's the whole point of "functional" as a concept. It cleanly separates the result of a computation from the process used to produce that result.
So one of your "simple" explanations must be wrong.
So?
> And if-then statements are functorial.
So?
All the "this is hard" stuff around these ideas seems to focus on managing to explain what these things are but I found that to progress at the speed of reading (so, about as easy as anything can be) once it occurred to me to find explanations that used examples in languages I was familiar with, instead of Haskell or Haskell-inspired pseudocode.
What I came out the other side of this with was: OK, I see what these are (that's incredibly simple, it turns out) and I even see how these ideas would be useful in Haskell and some similar languages, because they solve problems with and help one communicate about problems particular to those languages. I do not see why it matters for... anything else, unless I were to go out of my way to find reasons to apply these ideas (and why would I do that? And no, I don't find "to make your code more purely-functional" a compelling reason, I'm entirely fine with code I touch only selectively, sometimes engaging with or in any of that sort of thing).
The "so?" is the part I found (and find) hard.
For example, The natural numbers form a ring and field over normal addition and multiplication , but you don't need to know ring theory to add numbers..
People need to stop worrying about not understanding things. No one understands everything.
And you are correct. Monads have nothing to do with sequencing (I mean any more than any other non commutative operator -- remember x^2 is not the same as 2^x)
Haskell handles sequencing by reducing to weak head normal form which is controlled by case matching. There is no connection to monads in general. The IO monad uses case matching in its implementation of flatmap to achieve a sensible ordering.
As for JavaScript flat map, a.flatMap(b).flatMap(c) is the same as a.flatMap(function (x) { return b(x).flatMap(c);}).
This is the same as promises: a.then(b).then(c) is the same as a.then(function (x) { return b(x).then(c)}).
Literally everything for which this is true forms a monad and the monad laws apply.
I'm not worried, but it's amusing to see this person say it's so simple, and then immediately trample on it.
Casual attempts at defining Monads often just sweep a pile of confusion around a room for a while, until everything gets hidden behind whatever odd piece of furniture that is familiar to the person generating the definition. They then imagine they have cleared up the confusion, but it is still there.
A monad is a generalization of all them. It's a structure that covers values of type T, some "payload" (maybe one, like Promise, maybe many, like List, maybe even none, like List or Optional sometimes). You can ask it to do some operations on these values "inside", it's the map() operation. You can ask it to do similar thing when operation on each value produces a nested structure of the same kind, and flatten them into one level again: this is flatMap(). This is how Promises are chained. The result is again a structure of the same kind, maybe with "payload" of a different type.
This is a really simple abstraction, simpler than most GoF patterns, to my mind, and more fundamental and useful.
Introduction
As Dan Doel points out here, the gadget Free that turns a functor into a monad is itself a kind of monad, though not the usual kind of monad we find in Haskell. I'll call it a higher order monad and you can find a type class corresponding to this in various places including an old version of Ed Kmett's category-extras. I'll borrow some code from there. I hunted around and couldn't find an implementation of Free as an instance of this class so I thought I'd plug the gap.
> {-# LANGUAGE RankNTypes, FlexibleContexts, InstanceSigs, ScopedTypeVariables #-}
import Control.Monad import Data.Monoid
To make things unambiguous I'll implement free monads in the usual way here:
> data Free f a = Pure a | Free (f (Free f a))
instance Functor f => Functor (Free f) where fmap f (Pure a) = Pure (f a) fmap f (Free a) = Free (fmap (fmap f) a)
instance Functor f => Monad (Free f) where return = Pure Pure a >>= f = f a Free a >>= f = Free (fmap (>>= f) a)
The usual Haskell typeclass
Monad
corresponds to monads in the category of types and functions,
Hask
. We're going to want monads in the category of endomorphisms of
Hask
which I'll call
Endo
.
The objects in Endo correspond to Haskell's Functor. The arrows in Endo are the natural transformations between these functors:
> type Natural f g = (Functor f, Functor g) => forall a. f a -> g a
So now we are led to consider functors in
Endo
.
> class HFunctor f where
A functor in
Endo
must map functors in
Hask
to functors in
Hask
. So if
f
is a functor in
Endo
and
g
is a functor in
Hask
, then
f g
must be another functor in
Hask
. So there must be an
fmap
associated with this new functor. There's an associated
fmap
for every
g
and we collect them all into one big happy natural family:
> ffmap :: Functor g => (a -> b) -> f g a -> f g b
But note also that by virtue of being a functor itself,
f
must have its own
fmap
type function associated with it. The arrows in
Endo
are natural transformations in
Hask
so the
fmap
for
HFunctor
must take arrows in
Endo
to arrows in
Endo
like so:
> hfmap :: (Functor g, Functor h) => Natural g h -> Natural (f g) (f h)
Many constructions in the category
Hask
carry over to
Endo
. In
Hask
we can form a product of type types
a
and
b
as
(a, b)
. In
Endo
we form the product of two functors
f
and
g
as
> data Product f g a = Product (f (g a))
Note that this product isn't commutative. We don't necessarily have an isomorphism from
Product f g
to
Product g f
. (This breaks many attempts to transfer constructions from
Hask
to
Endo
.) We also won't explicitly use
Product
because we can simply use the usual Haskell composition of functors inline.
We can implement some functions that act on product types in both senses of the word "product":
> left :: (a -> c) -> (a, b) -> (c, b)
left f (a, b) = (f a, b)
right :: (b -> c) -> (a, b) -> (a, c) right f (a, b) = (a, f b)
hleft :: (Functor a, Functor b, Functor c) => Natural a c -> a (b x) -> c (b x) hleft f = f
hright :: (Functor a, Functor b, Functor c) => Natural b c -> a (b x) -> a (c x) hright f = fmap f
(Compare with what I wrote here.)
We have something in Endo a bit like the type with one element in Hask, namely the identity functor. The product of a type a with the one element type in Hask gives you something isomorphic to a. In Endo the product is composition for which the identity functor is the identity. (Two different meanings of the word "identity" there.)
We also have sums. For example, if we define a functor like so
> data F a = A a | B a a
we can think of
F
as a sum of two functors: one with a single constructor
A
and another with constructor
B
.
We can now think about reproducing an Endo flavoured version of lists. The usual definition is isomorphic to:
> data List a = Nil | Cons a (List a)
And it has a
Monoid
instance:
> instance Monoid (List a) where
mempty = Nil mappend Nil as = as mappend (Cons a as) bs = Cons a (mappend as bs)
We can try to translate that into
Endo
. The
Nil
part can be thought of as being an element of a type with one element so it should become the identity functor. The
Cons a (List a)
part is a product of
a
and
List a
so that should get replaced by a composition. So we expect to see something vaguely like:
List' a = Nil' | Cons' (a (List' a))
That's not quite right because
List' a
is a functor, not a type, and so acts on types. So a better definition would be:
List' a b = Nil' b | Cons' (a (List' a b))
That's just the definition of
Free
. So free monads are lists in
Endo
. As everyone knows :-) monads are just monoids in the category of endofunctors. Free monads are also just free monoids in the category of endofunctors.
So now we can expect many constructions associated with monoids and lists to carry over to monads and free monads.
An obvious one is the generalization of the singleton map a -> List a:
> singleton :: a -> List a
singleton a = Cons a Nil
hsingleton :: Natural f (Free f) hsingleton f = Free (fmap Pure f)
Another is the generalization of
foldMap
. This can be found under a variety of names in the various free monad libraries out there but this implementation is designed to highlight the similarity between monoids and monads:
> foldMap :: Monoid m => (a -> m) -> List a -> m
foldMap _ Nil = mempty foldMap f (Cons a as) = uncurry mappend $ left f $ right (foldMap f) (a, as)
fold :: Monoid m => List m -> m fold = foldMap id
hFoldMap :: (Functor f, Functor m, Monad m) => Natural f m -> Natural (Free f) m hFoldMap _ (Pure x) = return x hFoldMap f (Free x) = join $ hleft f $ hright (hFoldMap f) x
hFold :: Monad f => Natural (Free f) f hFold = hFoldMap id
The similarity here isn't simply formal. If you think of a list as a sequence of instructions then
foldMap
interprets the sequence of instructions like a computer program. Similarly
hFoldMap
can be used to interpret programs for which the free monad provides an abstract syntax tree.
You'll find some of these functions here by different names.
Now we can consider Free. It's easy to show this is a HFunctor by copying a suitable definition for List:
> instance Functor List where
fmap f = foldMap (singleton . f)
instance HFunctor Free where ffmap = fmap hfmap f = hFoldMap (hsingleton . f)
We can define
HMonad
as follows:
> class HMonad m where
hreturn :: Functor f => f a -> m f a hbind :: (Functor f, Functor g) => m f a -> Natural f (m g) -> m g a
Before making
Free
an instance, let's look at how we'd make
List
an instance of
Monad
> instance Monad List where
return = singleton m >>= f = fold (fmap f m)
And now the instance I promised at the beginning.
> instance HMonad Free where
hreturn = hsingleton hbind m f = hFold (hfmap f m)
I've skipped the proofs that the monad laws hold and that
hreturn
and
hbind
are actually natural transformations in
Endo
. Maybe I'll leave those as exercises for the reader.
Update
After writing this I tried googling for "instance HMonad Free" and I found this by haasn. There's some other good stuff in there too.