Question

I thought I had a good handle on Haskell Monads until I realized this very simple piece of code made no sense to me (this is from the haskell wiki about the State monad):

playGame :: String -> State GameState GameValue
playGame []     = do
  (_, score) <- get
  return score

What confuses me is, why is the code allowed to call "get", when the only argument supplied is a string? It seems almost like it is pulling the value out of thin air.

A better way for me to ask the question may be, how would one rewrite this function using >>= and lambda's instead of do notation? I'm unable to figure it out myself.

Was it helpful?

Solution

Desugaring this into do notation would look like

 playGame [] =
   get >>= \ (_, score) ->
   return score

We could also just write this with fmap

 playGame [] = fmap (\(_, score) -> score) get
 playGame [] = fmap snd get

Now the trick is to realize that get is a value like any other with the type

 State s s

What get will return won't be determined until we feed our computation to runState or similar where we provide an explicit starting value for our state.

If we simplify this further and get rid of the state monad we'd have

playGame :: String -> (GameState -> (GameState, GameValue))
playGame [] = \gamestate -> (gamestate, snd gamestate)

The state monad is just wrapping around all of this manual passing of GameState but you can think of get as accessing the value that our "function" was passed.

OTHER TIPS

A monad is a "thing" which takes a context (we call it m) and which "produces" a value, while still respecting the monad laws. We can think of this in terms of being "inside" and "outside" of the monad. The monad laws tell us how to deal with a "round trip" -- going outside and then coming back inside. In particular, the laws tell us that m (m a) is essentially the same type as (m a).

The point is that a monad is a generalization of this round-tripping thing. join squashes (m (m a))'s into (m a)'s, and (>>=) pulls a value out of the monad and applies a function into the monad. Put another way, it applies a function (f :: a -> m b) to the a in (m a) -- which yields an (m (m b)), and then squashes that via join to get our (m b).

So what does this have to do with 'get' and objects?

Well, do notation sets us up so that the result of a computation is in our monad. And (<-) lets us pull a value out of a monad, so that we can bind it to a function, while still nominally being inside of the monad. So, for example:

doStuff = do
   a <- get
   b <- get
   return $ (a + b)

Notice that a and b are pure. They are "outside" of the get, because we actually peeked inside it. But now that we have a value outside of the monad, we need to do something with it (+) and then stick it back in the monad.

This is just a little bit of suggestive notation, but it might be nice if we could do:

doStuff = do
  a       <- get
  b       <- get
  (a + b) -> (\x -> return x) 

to really emphasize the back and forth of it. When you finish a monad action, you must be on the right column in that table, because when the action is done, 'join' will get called to flatten the layers. (At least, conceptually)

Oh, right, objects. Well, obviously, an OO language basically lives and breathes in an IO monad of some kind. But we can actually break it down some more. When you run something along the lines of:

 x = foo.bar.baz.bin()

you are basically running a monad transformer stack, which takes an IO context, which produces a foo context, which produces a bar context, which produces a baz context, which produces a bin context. And then the runtime system "calls" join on this thing as many times as needed. Notice how well this idea meshes with "call stacks". And indeed, this is why it is called a "monad transformer stack" on the haskell side. It is a stack of monadic contexts.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top