Functors, Applicatives, and Monads in Pictures (2013)
1–10 of 56 posts
Re: Functors, Applicatives, and Monads in Pictures (2013)
#2Sure, it's very easy for me to understand monads mathematically, what kind of structure are they. I don't need any pictures for that, the definition suffices for me.
But that doesn't tell me what are they good for. We invent things for a reason, and I don't see the reason. Now I know the reason, it's been told to me many times (some way to wrap I/O while preserving functional purity), but I just don't see how that works.
I would love to find a resource that would explain these things for me in a way I'd understand them.
Re: Functors, Applicatives, and Monads in Pictures (2013)
#3Nice pictures, but I just don't get it. Sure, it's very easy for me to understand monads mathematically, what kind of structure are they. I don't need any pictures for that, the definition suffices for me. But that doesn't tell me what are they good for . We invent things for a reason, and I don't see the reason. Now I know the reason, it's been told to me many times (some way to wrap I/O while preserving functional…
The core useful operator for monads in Haskell is >>=. It has the following type:
m a -> (a -> m b) -> m b
If you squint, this is like function application with a few extra m's thrown in. Here's normal function application for comparison: a -> (a -> b) -> b
So what is this extra m useful for? It's like a hole where we get to plug in some custom logic. In a sense, it lets us change what it means to "apply" a "function". This turns out to be useful for a whole bunch of things, not just IO. (Honestly, from a pedagogical standpoint, I think IO is a bit of a distraction!)For example, take the Maybe type. It's Haskell's nullable: a Maybe a means you either have an a or Nothing.
data Maybe a = Just a | Nothing
Remembering the signature of >>= above, what's the most natural way to implement "application"? If we break the function into cases, it becomes pretty straightforward. Here's the specialized function signature we want to implement: (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
x >>= f = ...
If x is Nothing then we don't have anything to pass into f so the whole result has to be Nothing. If x has a value, we can just get that value out and pass it into f normally, returning the final result. Nothing >>= f = Nothing
Just x >>= f = f x
(In case you're not familiar with Haskell syntax, the above is actually a valid definition of (>>=) for Maybe!)For Maybe, being a monad gives us a standard way of working with values while automatically dealing with Nothing. It abstracts over repetitive null checking and lets us easily build up Maybe values based on other Maybe values.
Other examples of monads are the same in spirit. The list monad, for example, lets us handle any number of inputs in a way that's similar to Maybe. The State monad similarly lets us combine values while carrying along an implicit state internally.
The rest of the Monad structure (namely the return function and the laws) are just a formal way of codifying behavior behavior that's already intuitive.
So how does this all apply to doing input and output?
Well, the problem in Haskell is that it's a language of evaluating expressions at heart: executing effects makes no sense any more than it would in arithmetic. To work with effects we instead have a special, opaque type IO; normal expressions get evaluated to IO actions that can be run to produce the desired effect—namely the IO type.
Critically, the IO type does not have to be a monad. It could be completely self-contained and have custom functions for doing one action after the other. We could imagine something like:
after :: IO a -> IO b -> IO b
which would let you run an IO statement then run a second one and only return the value of the last one. It's like an imperative block of code!However, we would also like some way of using the results of an IO statement, perhaps assigning them to a name. We can't do this normally because the IO statements are run separately from expression evaluation. We'd have to have some sort of function that could take an IO value, unwrap it and do something with it. And how would we express an interface like this? With a normal function!
doSomething :: IO a -> (a -> IO b) -> IO b
Hey, doesn't that look familiar? It's exactly (>>=)!I'm hand-waving a bit again, but the rest of the monad structure comes up when you try to make sure after behaves consistently and intuitively.
So IO being a monad emerges naturally from the desire to be able to compose actions and depend on their results in a way that's separate from normal variable bindings and expression evaluation.
The causation here is important: it's not that IO is a monad, but rather the IO type (which could exist on its own) happens to naturally and usefully form a monad. But it does a lot of other things too, including some specific capabilities (like spawning threads) that are hard to generalize.
So my point, I suppose, is twofold: monads are useful for combining some notion of computation and IO happens to be an interesting example, but the fact that we wrap statements with external effects in a custom type called IO does not inextricably depend on the idea of a monad.
Did that explanation help? I wrote a blog post on a similar topic that might be interesting too: http://jelv.is/blog/Haskell-Monads-and-Purity
Re: Functors, Applicatives, and Monads in Pictures (2013)
#4Nice pictures, but I just don't get it. Sure, it's very easy for me to understand monads mathematically, what kind of structure are they. I don't need any pictures for that, the definition suffices for me. But that doesn't tell me what are they good for . We invent things for a reason, and I don't see the reason. Now I know the reason, it's been told to me many times (some way to wrap I/O while preserving functional…
The reason that the Monad structure is interesting, in my view, is that it's simple and neatly captures the notion of a "computation" that we can compose in different ways. The core useful operator for monads in Haskell is >>=. It has the following type: m a -> (a -> m b) -> m b If you squint, this is like function application with a few extra m's thrown in. Here's normal function application for comparison: a -> (a…
My man tikhonj... thanks.
Re: Functors, Applicatives, and Monads in Pictures (2013)
#5Nice pictures, but I just don't get it. Sure, it's very easy for me to understand monads mathematically, what kind of structure are they. I don't need any pictures for that, the definition suffices for me. But that doesn't tell me what are they good for . We invent things for a reason, and I don't see the reason. Now I know the reason, it's been told to me many times (some way to wrap I/O while preserving functional…
So I think that expanding your idea of what monads do might help. Monads enforce a sequencing, and let later computations in the sequence depend on the result of an earlier one. Have a look at the definition of the Monad instance for Maybe:
instance Monad Maybe where
return x = Just x
(Just x) >>= k = k x
Nothing >>= _ = Nothing
Now consider (because it's a contrived but simple example) that you have some `Map` type (from Strings to Strings, just for convenience), a value of that type `myMap :: Map` and a function `lookup :: Map -> String -> Maybe String`. Let's do the equivalent of `myMap[myMap[myMap["a"]]]`: case lookup myMap "a" of
Nothing -> Nothing
Just v -> case lookup myMap v of
Nothing -> Nothing
Just v' -> lookup myMap v'
This pattern of 1. do a thing, 2. check its result, 3. feed the result into the next step of the computation is what's abstracted over by the monad. We can write the same lookup using `do`-notation: do
v
If this is making sense, I'd suggest repeating the exercise with `Either`, which is often used to pass an error message on its `Left` constructor (bypassing the rest of the computation). Actual results are stored on the `Right` constructor. If that makes sense, then I would then look at `Reader` (which lets you do computations with some value (like an environmental context) at-hand, and then maybe `State`.If all the functional stuff is clear, then I'd look at the `STM` monad, which implements Software Transactional Memory. STM is IO-like in the sense that you are manipulating shared state, but you only have a restricted set of tools to do it with - the type `STM a` means "a transaction that fiddles with some shared memory, then returns a value of type `a`". To actually execute the transaction, you have to turn it into an `IO` action using the function `atomically :: STM a -> IO a` and put it in a side-effecting computation somewhere.
Hopefully that clears things up a bit: `IO` is just a special case of this sequencing strategy, but the really cool things happen because we can define what sequencing computations means for different data types. I think this is what some haskellers mean when they say "monads let you overload semicolons".
Re: Functors, Applicatives, and Monads in Pictures (2013)
#6Nice pictures, but I just don't get it. Sure, it's very easy for me to understand monads mathematically, what kind of structure are they. I don't need any pictures for that, the definition suffices for me. But that doesn't tell me what are they good for . We invent things for a reason, and I don't see the reason. Now I know the reason, it's been told to me many times (some way to wrap I/O while preserving functional…
The reason that the Monad structure is interesting, in my view, is that it's simple and neatly captures the notion of a "computation" that we can compose in different ways. The core useful operator for monads in Haskell is >>=. It has the following type: m a -> (a -> m b) -> m b If you squint, this is like function application with a few extra m's thrown in. Here's normal function application for comparison: a -> (a…
One problem I suppose is that not everyone speaks the abstract language of monads and so it does not serve much usefulness, yet, and maybe there is a little chicken and the egg with that.
Re: Functors, Applicatives, and Monads in Pictures (2013)
#7Nice pictures, but I just don't get it. Sure, it's very easy for me to understand monads mathematically, what kind of structure are they. I don't need any pictures for that, the definition suffices for me. But that doesn't tell me what are they good for . We invent things for a reason, and I don't see the reason. Now I know the reason, it's been told to me many times (some way to wrap I/O while preserving functional…
The reason that the Monad structure is interesting, in my view, is that it's simple and neatly captures the notion of a "computation" that we can compose in different ways. The core useful operator for monads in Haskell is >>=. It has the following type: m a -> (a -> m b) -> m b If you squint, this is like function application with a few extra m's thrown in. Here's normal function application for comparison: a -> (a…
The idea that the `IO` type forms a monad (as opposed to `IO` being a monad just because the wizards said so) is also really important and missing from my explanation. Great work.
Re: Functors, Applicatives, and Monads in Pictures (2013)
#8Earlier quoted context omitted.
The reason that the Monad structure is interesting, in my view, is that it's simple and neatly captures the notion of a "computation" that we can compose in different ways. The core useful operator for monads in Haskell is >>=. It has the following type: m a -> (a -> m b) -> m b If you squint, this is like function application with a few extra m's thrown in. Here's normal function application for comparison: a -> (a…
I appreciate your explanation but again it fails to actually show it being useful outside of the context of working around Haskell's strict type system. What the OP and myself would like to see is concrete examples why this is a better approach than the way something would be done without explicitly caring about monads (I say explicitly because I know there is a tendency to say that some given structure is a monad an…
Re: Functors, Applicatives, and Monads in Pictures (2013)
#9Nice pictures, but I just don't get it. Sure, it's very easy for me to understand monads mathematically, what kind of structure are they. I don't need any pictures for that, the definition suffices for me. But that doesn't tell me what are they good for . We invent things for a reason, and I don't see the reason. Now I know the reason, it's been told to me many times (some way to wrap I/O while preserving functional…
That's an excessively narrow reason. Its certainly a factor of why monads are front-and-center in Haskell, given the goals of the language, but its not really the reason monads are interesting or useful. Monads (and Functors and Applicatives, as well, as more general constructs) are interesting constructs in programming because they are powerful abstractions that unite disparate, useful data types and which, therefore, allow code that works across those data types. They therefore enable library code in circumstances where, in languages without such abstractions, fill-in-the-blanks template code patterns would be required, so they promote code reuse over copy-and-paste coding.
That IO operations are among the things that can be represented by monads is certainly part of their usefulness, but if IO was all they were good for, they wouldn't be all that interesting.
Re: Functors, Applicatives, and Monads in Pictures (2013)
#10Nice pictures, but I just don't get it. Sure, it's very easy for me to understand monads mathematically, what kind of structure are they. I don't need any pictures for that, the definition suffices for me. But that doesn't tell me what are they good for . We invent things for a reason, and I don't see the reason. Now I know the reason, it's been told to me many times (some way to wrap I/O while preserving functional…
Same goes for monads. If we have N data types and M functions, instead of writing N×M implementations, we can write just N monad instances + M generic implementations.
So it’s kind of tautological, but monads are basically useful because lots of useful things happen to form monads—exceptions, loggers, parsers, dependency injection, persistent state operations, continuations, futures, STM transactions, I/O actions, and so on.
If you can write a data type that represents an API, and implement a couple of interfaces, you get a complete, expressive EDSL for free.
With Facebook’s Haxl, for example, you can write ordinary serial-looking I/O code, and with just a few typeclass instances, instantly get concurrent/async data fetching without changing a single line of business logic. You can’t readily do that without the kind of first-class effects that monads provide.