The other day I was talking about functional programming - especially Haskell with some Java/Scala guys and they asked me what are Monads and where are they necessary.
Well the definition and examples were not that hard - Maybe Monad
, IO Monad
, State Monad
etc., so everyone was, at least partially, ok with me saying Monads are a good thing.
But where are Monads necessary - Maybe
can be avoided via magic values like -1
in the setting of Integer
, or ""
in the setting of String
. I have written a game without the State
Monad, which is not nice at all but beginners do that.
So my question: Where are Monads necessary ? - and cannot be avoided at all. (And no confusion - I like Monads and use them, I just want to know).
I think I have to clarify that I do not think using "Magic Values" is a good solution, but a lot of programmers use them, especially in low level languages as C or in SHell scrips where an error is often implied by returning -1
.
It was already clear to me that not using monads isn't a good idea. Abstraction is often very helpful, but also complicated to get, hence many a people struggle with the concept of monads.
The very core of my question was if it was possible to do for example IO
, without a monad and still being pure and functional. I knew it would be tedious and painful to put a known good solution aside, as well as lighting a fire with flint and tinder instead of using a lighter.
The article @Antal S-Z refers to is great you could have invented monads, I skimmed over it, and will definitely read it when I have more time. The more revealing answer is hidden in the comment with the blog post referred to by @Antal S-Z i remember the time before monads, which was the stuff I was looking for when I asked the question.
I don't think you ever need monads. They're just a pattern that shows up naturally when you're working with certain kinds of function. The best explanation of this point of view that I've ever seen is Dan Piponi (sigfpe)'s excellent blog post "You Could Have Invented Monads! (And Maybe You Already Have.)", which this answer is inspired by.
You say you wrote a game without using the state monad. What did it look like? There's a good chance you ended up working with functions with types that looked something like openChest :: Player -> Location -> (Item,Player)
(which opens a chest, maybe damages the player with a trap, and returns the found item). Once you need to combine those, you can either do so manually (let (item,player') = openChest player loc ; (x,player'') = func2 player' y in ...
) or reimplement the state monad's >>=
operator.
Or suppose that we're working in a language with hash maps/associative arrays, and we're not working with monads. We need to look up a few items and work with them; maybe we're trying to send a message between two users.
send username1 username2 = {
user1 = users[username1]
user2 = users[username2]
sendMessage user1 user2 messageBody
}
But wait, this won't work; username1
and username2
might be missing, in which case they'll be nil
or -1
or something instead of the desired value. Or maybe looking up a key in an associative array returns a value of type Maybe a
, so this will even be a type error. Instead, we've got to write something like
send username1 username2 = {
user1 = users[username1]
if (user1 == nil) return
user2 = users[username2]
if (user2 == nil) return
sendMessage user1 user2 messageBody
}
Or, using Maybe
,
send username1 username2 =
case users[username1] of
Just user1 -> case users[username2] of
Just user2 -> Just $ sendMessage user1 user2 messageBody
Nothing -> Nothing
Nothing -> Nothing
Ick! This is messy and overly nested. So we define some sort of function which combines possibly-failing actions. Maybe something like
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
f >>= Just x = f x
f >>= Nothing = Nothing
So you can write
send username1 username2 =
users[username1] >>= $ \user1 ->
users[username2] >>= $ \user2 ->
Just (sendMessage user1 user2 messageBody)
If you really didn't want to use Maybe
, then you could implement
f >>= x = if x == nil then nil else f x
The same principle applies.
Really, though, I recommend reading "You Could Have Invented Monads!" It's where I got this intuition for monads, and explains it better and in more detail. Monads arise naturally when working with certain types. Sometimes you make that structure explicit and sometimes you don't, but just because you're refraining from it doesn't mean it's not there. You never need to use monads in the sense that you don't need to work with that specific structure, but often it's a natural thing to do. And recognizing the common pattern, here as in many other things, can allow you to write some nicely general code.
(Also, as the second example I used shows, note that you've thrown the baby out with the bathwater by replacing Maybe
with magic values. Just because Maybe
is a monad doesn't mean you have to use it like one; lists are also monads, as are functions (of the form r ->
), but you don't propose getting rid of them! :-))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With