I love this article. I've touched all of these languages at least once with the exception of TypeScript which I would like to try. I really liked Elixir and its Supervisor tree, it definitely stretched my brain. I put in a strong effort to get good at Rust (the borrow checker won the battle, but the war is far from over). I couldn't stretch my brain far enough to grasp Monads and Functors for Haskell though. That one finally broke me.
I hope these languages keep growing. I'll keep them in my tool chest.
I'm always sad to see people get tripped up by Monads, and especially by Functors. They're necessarily simple concepts that are almost always explained poorly.
Functors are things you can map/apply a function over. Exactly no more, and exactly no less. Lists are Functors because you can apply a function to each element of a list. Maybe is a Functor because you can apply a function to a single element after checking if it exists. Functions are Functors because you can apply a function to the result of a function. 'Void a' is a Functor because you can apply a function 0 times to something that does not exist. The Functor typeclass exists so that we can say that we don't care which of these (or other) things we're dealing with. If you have a structure, and you can write some plumbing code to take a function and apply it around your structure, you have a Functor. Note that something either is or is not a Functor, it's not actually up to you to decide, just like you don't get to say the number 3 is even. It is, in essence, simpler than actually thinking about the particular structure you are mapping over, because that particular structure has additional implications beyond what Functor implies.
Monads are not quite as simple, but almost. They're usually explained in terms of return and bind on some bizarre example ("Monads are like burritos!"), but bind is a more complicated operation that's implemented for performance reasons, rather than ease of understanding. Functors need one thing (mapping) but Monads need 3. First, they need Functorness - if it's not a Functor, it can't be a Monad. Second, they need to be Pointed, or injectable - if you have some random value, there should be a trivial way to cram it into the Monad and this is currently called 'return', but in the future might be called 'pure'. Lastly, they need Join, or flattenability. The injectability means you can just keep wrapping more and more layers of the Monad around a value - you can have the number 1, a single element list containing the number 1, a single element list containing a single element list containing the number 1, and so on ad infinitum. Join does the opposite, save that it can't get rid of the last layer - you don't need to know how to 'exit' the Monad, just simplify it. In Haskell syntax, that means you know how to go from [] to , but you don't need to go all the way to 1. That's it, those are the three operations that make a Monad. There are some rules about how Monad instances should behave - Functor had them too but the type system is good enough that you can't accidentally break those. The rules are 1) that 'return' must really be trivial - if you stack a bunch of layers with return, then join them back down, nothing should change and 2) if you have a bunch of layers, the order you join them in should not matter to the final result. In terms of list, return makes a single element list, and join is just a single layer flatten - and it doesn't matter what order you flatten things in, you always get the same list at the end. In equational terms, 'x == join (return x) == join (fmap return x)' and 'join (join v) == join (fmap join v)'. If this sounds scary because of 'fmap' here, just try to remember that by using fmap we remove all possible considerations except that it's something you can map over - it is mapping distilled to its most basic possible form. Individual Monads have their own interesting behaviors, but those have nothing to do with Monad itself, which is nothing more than what I have described here. Monad is ultimately a fairly simple concept that gets bogged down with specifics that you don't need to think about for the core subject. Monads are implemented in terms of bind, where 'm >>= f' is ultimately the same meaning as 'join (fmap f m)', but the latter possibly constructs and breaks down an intermediate structure, and so can be prohibitively slow in practice.
Typically, the aha moment for Monads is not when people grasp the complexity, but rather fully realize they can let the complexity go.
Thanks WarDraft! I'm still unsure about Monads lol, but your last statement put me at ease. Maybe it's best to not try so hard to understand it, and let it show me 👍🏾
We're a place where coders share, stay up-to-date and grow their careers.
We strive for transparency and don't collect excess data.