1

I am currently trying to understand the do notation as taught in: http://learnyouahaskell.com/for-a-few-monads-more ...Here it is applied to simple functions and not a monad.

type Stack = [Int]

push :: Int -> Stack -> ((), Stack)
push a stack = ((),a:stack)

pop :: Stack -> (Int, Stack)
pop (x:xs) = (x, xs)

I dont understand the do-syntax fully, I am curious: Is this

turnipStack2 :: Stack -> (Int, Stack)
turnipStack = do
  push 3 
  c <- pop
  pop

.. the same as

turnipStack :: Stack -> (Int, Stack)
turnipStack = do
  push 3 
  c <- pop
  return c

If so, why is that. And could I have done the first mentioned without writing pop in the end. In that case I get an error although I don't understand it.

Piskator
  • 605
  • 1
  • 9
  • "without writing pop in the end" The `<-` in do-notation is not really a function, but syntax sugar that is desugared to actual functions like `>>=`. And the desugaring requires that some monadic statement exists below the `<-`. – danidiaz Nov 14 '22 at 18:53
  • "What do you mean by 'desugared to actual functions like `>>=` ". Syntactic sugar here means something that is easier to read for humans, but entails some more complex operation. As mentioned in my comment below I use it equivalent to the LYAH example 1:1 (stackManip example). – Piskator Nov 15 '22 at 08:55
  • @danidiaz and what does monadic statements encompass in this regard? Would those be `fail`, `return` and the binding-operators? – Piskator Nov 15 '22 at 09:17
  • @Piscator "something that is easier to read for humans, but entails some more complex operation". Yes, that's what do-notation is. https://stackoverflow.com/questions/54577848/haskell-desugaring-state-do-notation https://stackoverflow.com/questions/39614816/monadic-desugaring-haskell It lets you use imperative-looking syntax to combine monadic values. You can ask ghci for the type of `>>=` with `:type (>>=)`, you can pass `>>=` as an argument to a suitable higher-order function, but you can't do that with `<-` or an isolated `do`. Also `return` is not syntax sugar, but just another function. – danidiaz Nov 15 '22 at 19:00

1 Answers1

3

First, to actually get this working you need to change the signatures to use the state monad. Currently your do block refers to the (Stack->) monad, aka Reader Stack, but you want State Stack instead.

import Control.Monad.Trans.State

push :: Int -> State Stack ()
push a = state $ \stack -> ((),a:stack)

pop :: State Stack Int
pop = state $ \(x:xs) -> (x, xs)

turnipStack2 :: State Stack Int
turnipStack2 = do
  push 3 
  c <- pop
  pop

This is not equivalent to

 do
  push 3
  c <- pop
  return c

The latter just pops off the one element you stored again, and then gives it back. This could actually be simplified to

 do
  push 3
  pop

thanks to the monad law do{ x<-a; return x } ≡ a (right identity).

By contrast, turnipStack2 first pops off the 3 you pushed, but doesn't use it (it's just discarded) then pops off another element which is the result value.

Applying the right identity law in reverse, you could however write it thus:

 do
  push 3 
  _ <- pop
  d <- pop
  return d

Binding an unused variable can be omitted, so

turnipStack2 = do
  push 3 
  pop
  d <- pop
  return d
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • I am not sure I understand. In the LYAH they give the example of stackManip which is exactly equal to my turnipStack2. Are you saying that it will not work without the `stack` Monad? Furthermore when I call my two functions the output seems to be equivalent. I dont know if that has to do with that it is using a `Reader` rather than `State Stack`-Monad. @danidiaz points out something above. I don't know if he is referring to that you can use `do`-notation on a function that is not monadic without getting the benefits of the short cut (sugar)that the `do`-notation will be on an actual monad. – Piskator Nov 15 '22 at 08:49
  • @Piskator well, your two functions give the same result because you're working in the wrong monad – you're (accidentally) using the reader monad, which just takes the stack as a constant environment parameter. This would have become obvious if you had tried to do more with the popped values, since the types don't actually make sense. – leftaroundabout Nov 15 '22 at 08:58
  • Does it use a 'reader Monad' by default unless I deliberately declare that it is for instance a State Monad I want to use? – Piskator Nov 21 '22 at 13:51
  • @Piskator not so much “by default” as, you forced it to use the function-monad by giving a function-signature – leftaroundabout Nov 22 '22 at 10:14
  • Ok, I think I understand partially; It uses a Reader Monad because the type signature for the function forced it to use `((->) r) `-Monad, that is the function-Monad. And the specific function I used also used a `Reader`-Monad as part of the functions that were used !? – Piskator Nov 22 '22 at 10:36
  • That sounds about right. – leftaroundabout Nov 22 '22 at 10:38