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 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.
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.
Bear with me for a bit, because I still don't get it (although I'm not the OP, I share the same doubts). In OO terms, if you have N types and M functions, with M different behaviours (function code), you aggregate the N types into an inheritance tree that makes you write 1xM functions (one function against the ancestor of the N types). If you have 2M behaviours, you aggregate the types in two different inheritance hierarchies and then write 2M functions. What expressiveness advantages do monads provide against this OO scenario?
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.
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
You know what else doesn't make sense in arithmetic? Computation. I think it is important to wonder how come mathematical definitions of computation didn't come about until the 20th century[1] even though math has had most of its big breakthroughs (to date) by then (or, at least, there was no revolution in mathematics in the 20th century on the same scale as there was in physics). And yet, as advanced as math was, computation was only defined very late in the game. The reason is that, perhaps ironically, the concept of computation is absent from most of math, which is "equational".
But computation is the very core of computer science. So, for example, in arithmetic, 4 and 2+2 are the same thing because 4 = 2 + 2. So in arithmetic, equality means "sameness". But in computer science they are most certainly not the same, because the process of moving from one of the representation to the other is precisely what a computation does. Not only that, the process isn't even necessarily symmetrical, as moving in one direction may entail a much higher computational complexity than going in the other direction.
Any distinction between calculations and effect (other than actual IO) is, therefore, arbitrary. Whether or not the particular distinctions Haskell makes (where waiting for one second may or may not be an effect depending on how it is implemented) in the form Haskell makes it -- using pure functions and equational reasoning and describing effects with monads -- is actually useful and beneficial is a question for psychologists. Given Haskell's design, monads are certainly useful in Haskell, but that utility can't be generalized to languages with other designs.
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.
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.
My man tikhonj... thanks.
Monads take care of a very common computation pattern: wrapping and unwrapping data from a container to do stuff with it. This process is error-prone and leads to code bloat if done by hand whenever needed.
I mean, how often have you had to apply a function to every element in a vector of values? fmap takes care of that without having to go into the list, take a value one at a time, and apply the function you want to use.
Rather than have to unwrap your container to use functions, a monad offers the means to transform functions to use a monadic container as input instead. No wrapping or unwrapping by hand required; using bind, fmap, or ap(ply), you transform lots of functions to use the monadic container and build a seamless pipeline for data to traverse.
How often have you had to deal with a value that might or might not exist--e.g. searching a string for a substring's position? If you had that problem, you might say, well, I'm going to return an integer, but a negative number means no match. And then in all code thereafter, I have to check if the integer is negative and do different stuff. A simple Maybe/Optional monad would take care of all that for you.
How often have you had to write verbose output alongside a computation? You could pepper your code with print statements, but maybe you want that output controlled by some runtime parameter. Would you want to check at every print statement whether that parameter is true? Or you could use a Writer monad, pass along the verbose output through the computation, with an easy means to transform regular functions into using Writers, and then decide what to do with the output at the end.
Sure, it's not convenient to try to use monads if the language doesn't offer it, or if there's not a library to facilitate it (trust me, I know this; I implemented a slew of templates to use monads in C++).
But programmers do this stuff all the time: unwrap data, do stuff with it, pack it back up. They do this over and over, repeating themselves, and such repetitive code is a maintenance trap waiting to happen.
Monads help you avoid that trap, and they help you take your program and reduce it to "one thing in, one thing out." Now maybe those "one things" are containers, but linearizing the flow of data this way only helps make the program's concept easier to understand.
Isn't this what Functor does with `fmap`, not Monad?
Even if Monad did not exist and you only had Functor and Applicative, you'd be able to compose `pure` and `fmap` to get the same effect as bind, right?
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 <- lookup myMap "a"
v' <- lookup myMap v
lookup myMap 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".
function_name :: Param1Type -> Param2Type -> Param3Type -> ReturnType
To me that just makes no sense. There's no way to logically "read" it. The natural way would be "function_name converts a Param1Type into a Param2Type and then ... what?! Is this a chain of functions?"Why not have something sensible like this?
function_name :: Param1Type, Param2Type, Param3Type -> ReturnTypeSo we can have a function which takes a single parameter which is a tuple :
function_name :: (Param1Type, Param2Type) -> ReturnType
Or we can have a function which takes a single parameter and returns another function: function_name :: Param1Type -> (Param2Type -> ReturnType)
Using implied operator precedence, the later is written: function_name :: Param1Type -> Param2Type -> ReturnType
And has to be distinguished from a function which takes a function as parameter: function_name :: (Param1Type -> Param2Type) -> ReturnTypeWhat we usually see is something like this:
f(x) = y
But if you look at it carefully the function f is a mapping of the domain x to the range y (the arrow is f), written like so: x -> y
Of course, the domain, being a set of all permissible values, is the type. So we write the type instead: type1 -> type2
Everything in Haskell is a function, and pure functions only ever take ONE parameter. Therefore the commas make no sense. The name in front just aids in naming stuff.If you have a function that takes 2 parameters, they'd have to be curried. How would you represent curried functions?
f::type1 -> type2 -> type3
To make it more concrete, let's look at add instead of f.So we can define a function like this (let's use Python for its readability):
def add(a, b): return a + b
But remember, in haskell, functions are pure. Meaning they only map one input to one output. In order to make this happen, we need to split the function up into parts that only take one parameter.Let's start with the plus operator as a function (it is one in Haskell just made into an infix). To think about it, it'd be something like this:
plus(a)(b)
Where plus(a) is defined as: def plus(a): return plusA
Hence the first part would become a curried function like so, which takes another parameter: plusA(b)
Where plusA() is defined as such: def plusA(b): return b + a # a is a constant
So if you look at it from the types it was being transformed from: plus() # takes a Real, returns plusA.
plusA(___) # takes a Real, returns a Real. written as (Real -> Real)
So if you put it together, the function signature for plus() is:plus() takes Real -
plus :: Real->
plus() returns plusA (which is Real->Real) - plus :: Real-> (Real-> Real)
Or in other words we can write it as such add :: Real -> Real -> Real fun :: A -> B -> C -> D
can be bracketed in a bunch of different ways - fun1 :: (A -> B) -> (C -> D)
fun2 :: A -> (B -> C -> D)
etc. and don't these all mean different things?fun1 would take a function and return a function, whereas fun2 takes an A and returns a curried function of B,C -> D.
Secondly, note that in Haskell a space represents function application -- it's not just there to separate terms.
This stuff might seem crazy, but once you start using it for useful things you come to really miss it in other languages. :)
Say we have a function with signature
f :: Int -> Int -> Int -> Int
Let's say this just add those Ints together.Now let's create another function by partially applying the initial function: f1 = f 42
f1 signature will be
f1 :: Int -> Int -> Int
Kind of "consuming" the signature. Repeat this until using up all the parameters. You end up with a function with no parameters, which map to a single Int.You could say that f3 -> Int (note, not a valid notation for the sake of example). No need for a different symbol.
function_name :: param1 -> param2 -> return
is a function that if you only give it param1 it returns a function that takes an argument of param2Type and returns returnType.
This way you can easily compose functionsf :: param1 -> (param2 -> return)
ret = (f p1) p2