Comment by hutao

20 days ago

A great way to understand monads is as a "design pattern," because they pop up extremely often in practical programming. Consider functions with a type signature that looks like `A -> T<B>`, such as `A -> Promise<B>` or `A -> Optional<B>`.

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.

This explanation doesn't contain any practical examples either. I mean, I do know that Promises are a monad, but that's not obviously helpful. You can write plenty of async code using Promises without knowing that, and it will probably be clearer to the reader. Similarly, options are a monad, but that's unhelpful for writing code that deals with optional values.

Why is most writing about functional programming like this?

  • The practical example is being able to use the same names and utility functions and such on all of the different monads. That's kind of it.

    Other than that it's just nice for communication in the right groups, it's shorthand for a whole bunch of properties that you then don't have to explain.

    It also only really works super well in languages where the type system is expressive enough to allow that (or just permissive enough not to stop you I guess), so that mostly comes up in fun "functional" languages where they spent a bunch of time on the type system.

    You'll probably understand a bit better if you take some time and learn/use Haskell a bit (if you don't already understand, it kind of sounds like you do tbh). It's a fun and educational language in a bunch of ways IMO. It depends on the kind of person/programmer you are if you'll really care though.

  • Because FP is useless so there aren't any practical examples. ...but all kidding aside, I kinda feel like an astronomer talking to an astrologer when it comes to this stuff. Like I'm scanning the night sky with my telescope while he's talking about Mars being 'in the house of Jupiter' or whatever. It's the old Abelson quote about computer science all over again.