Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When we should use do?

Tags:

haskell

Sometimes the program shows an error if I don't use do. But it runs well without do sometimes. ex:

countdown ::Int -> IO ()
countdown x =  if x <= 0 
               then putStrLn "The End."
               else putStrLn (show (x))

runs well But

countdown ::Int -> IO ()
countdown x =  if x <= 0 
               then putStrLn "The End."
               else putStrLn (show (x)) 
                    countdown (x-1)

shows error

like image 942
chen Crush Avatar asked Mar 18 '26 14:03

chen Crush


1 Answers

Short answer: line breaks don't mean "next statement" in Haskell, like they do in many mainstream languages, like Python, Ruby, and recently even JavaScript.


Long answer

First, let's get rid of the if. It only clouds the issue:

countdown1 :: Int -> IO ()
countdown1 x = putStrLn (show x)

countdown2 :: Int -> IO ()
countdown2 x = putStrLn (show x)
               countdown (x-1)

Notice that the type of your function is IO (). That's the type of what the function must ultimately calculate. Or "produce", if you will. This means that whatever is on the right of the equality sign must be of type IO ().

countdown x = .........
              ^       ^
              +-------+
                  \
                   the type of whatever is here must be `IO ()`

The first version, countdown1, does satisfy this, because the expression putStrLn (show x) is, indeed, of type IO ()

The second version, countdown2, on the other hand, looks very strange to the compiler. It looks like you're trying to call putStrLn, and you're trying to pass it three parameters: (show x), countdown, and (x-1).

Don't let the newline between (show x) and countdown confuse you: as mentioned above, newlines don't mean "next statement" in Haskell. This is because in Haskell there is no such thing as a "statement" in the first place. Everything is an expression.

But wait! If newlines don't count as "next statement", then how the hell do I tell Haskell to perform several actions in order? Like, for example, first do putStrLn, and then do countdown?

Well, this is where monads come in. Monads (of which IO is a prime example) were specifically brought into Haskell for this very reason: to express order of things. And the primary operation for that is "bind", which in Haskell exists in the form of an operator >>=. This operator takes a monadic value (such as IO Int or IO ()) on the left, and takes a function that returns a monadic value (such as Int -> IO String) on the right, and "glues" them together. The result is a new monadic action, which consists of the two input actions executed one after the other.

Applying this to your example with putStrLn and countdown, it would look like this:

putStrLn (show x) >>= (\y -> countdown (x-1))
^               ^     ^                     ^
+---------------+     +---------------------+
    \                        \
     first monadic value      a function that takes the result of the
                              first action as parameter and returns
                              the second action

But this is a bit inconvenient. Sure, you can glue together two actions, maybe even three. But after a while this becomes very messy. (at first I had an example here, but then decided to do without; just trust me: it does get messy)

So to relieve the mess, the language now offers syntactic sugar in the form of the do notation. Inside the do notation, newline does, in fact, mean "next statement", and these consecutive "statements" get desugared into a sequence of calls to the >>= operator, giving them the semantics of "executing in order". Something like this:

do
  y <- f x
  z <- h y            ====>      f x >>= (\y -> h y >>= (\z -> g x y z))
  g x y z

So to the naked eye the do notation does look roughly equivalent to multiple-line program in Python, Ruby, or JavaScript. But underneath it all, it's still a pure functional program, where everything is an expression, and the order of (non-pure, effectful) operations is explicitly controlled.


So, to summarize: you need to use do in your program to express the order - first putStrLn, and then countdown:

countdown :: Int -> IO ()
countdown x = if x <= 0 
              then putStrLn "The End."
              else do
                     putStrLn (show (x)) 
                     countdown (x-1)

But you don't need to use do when there is just one operation, so there is no order to speak of.

And if you don't want to use do for whatever reason, you can desugar it manually into the equivalent >>= call:

countdown :: Int -> IO ()
countdown x = if x <= 0 
              then putStrLn "The End."
              else putStrLn (show (x)) >>= \_ -> countdown (x-1)
like image 85
Fyodor Soikin Avatar answered Mar 21 '26 04:03

Fyodor Soikin