mherzl
mherzl

Reputation: 6210

Does Haskell's 'do' notation always involve a monad?

I had always figured that Haskell's do-notation was just sugar for monad composition (>>=). Then I came across this instance of it, and am not sure what monad is in use here, or whether there even is one:

rollDieThreeTimes :: (Die, Die, Die)
rollDieThreeTimes = do
  let s = mkStdGen 0
      (d1, s1) = randomR (1, 6) s
      (d2, s2) = randomR (1, 6) s1
      (d3, _) = randomR (1, 6) s2
  (intToDie d1, intToDie d2, intToDie d3)

intToDie :: Int -> Die
...

My questions:

Upvotes: 5

Views: 640

Answers (2)

Axman6
Axman6

Reputation: 907

One important thing to know about do-notation is that its rules are applied before type checking. Those rules are (roughly):

  • do { x; y } ==> x >> do { y } (with rules applied recursively)
  • do {x <- a; b } ==> a >>= \x -> do { b }
  • do {{pat} <- a; b } ==>
    a >>= \x -> case x of 
      {pat} -> do { b }
      _     -> fail "Pattern match fail"`
    
  • do { let {decls}; b } ==> let {decls} in do { b }
  • do { a } ==> a

those last two rules are the important ones here - any expression on its own which has no (implicit) semicolons will just become itself. So as long as you don't make use of the first two rules, you can have expressions of any type in your code, without the need for any monad. Following those rules, your code goes from

rollDieThreeTimes :: (Die, Die, Die)
rollDieThreeTimes = do
  let s = mkStdGen 0
      (d1, s1) = randomR (1, 6) s
      (d2, s2) = randomR (1, 6) s1
      (d3, _) = randomR (1, 6) s2
  (intToDie d1, intToDie d2, intToDie d3)

to

rollDieThreeTimes :: (Die, Die, Die)
rollDieThreeTimes = 
  let s = mkStdGen 0
      (d1, s1) = randomR (1, 6) s
      (d2, s2) = randomR (1, 6) s1
      (d3, _) = randomR (1, 6) s2
  in (intToDie d1, intToDie d2, intToDie d3)

and is then type checked.

Upvotes: 2

amalloy
amalloy

Reputation: 92067

do should always be used in a monadic context. But if you only have one expression in the do-block, then it desugars to just that expression, so there are no references to >>=, and no Monad constraint arises.

So you can write

do let x = a
       y = b
   foo

which is equivalent to

let x = a
    y = b
in foo

but really, you should just write the latter.

I haven't read HPFFP, which Google tells me is probably the book you got this example from. It comes from the chapter on State, and it seems like the author(s) introduce this as a simple state-less example, then later add state to it. Perhaps they chose this unusual construction for pedagogical reasons, for example to keep it similar to a later version using monadic binds in place of the let bindings.

Upvotes: 5

Related Questions