Dovahkiin
Dovahkiin

Reputation: 1056

What does Haskell's "do" keyword do?

I'm a C++/Java programmer, and I'm trying to learn Haskell (and functional programming in general), and I've been having a rough go at it. One thing I tried was this:

isEven :: Int -> Bool
isEven x =
    if mod x 2 == 0 then True
    else False

isOdd :: Int -> Bool
isOdd x =
    not (isEven x)

main =
    print (isEven 2)
    print (isOdd 2)

But this failed with this error during compilation:

ghc --make doubler.hs -o Main
[1 of 1] Compiling Main             ( doubler.hs, doubler.o )

doubler.hs:11:5: error:
    • Couldn't match expected type ‘(a0 -> IO ()) -> Bool -> t’
              with actual type ‘IO ()’
    • The function ‘print’ is applied to three arguments,
      but its type ‘Bool -> IO ()’ has only one
      In the expression: print (isEven 2) print (isOdd 2)
      In an equation for ‘main’: main = print (isEven 2) print (isOdd 2)
    • Relevant bindings include main :: t (bound at doubler.hs:10:1)
make: *** [all] Error 1

So, I saw some code online with the "do" keyword, so I tried it like this:

isEven :: Int -> Bool
isEven x =
    if mod x 2 == 0 then True
    else False

isOdd :: Int -> Bool
isOdd x =
    not (isEven x)

main = do
    print (isEven 2)
    print (isOdd 2)

And it worked exactly like I thought it should.

What's going on here? Why doesn't the first code snippet work? And what does adding "do" actually do?

PS. I saw something about "monads" on the internet related to the "do" keyword, does that have something to do with this?

Upvotes: 11

Views: 6806

Answers (4)

sepp2k
sepp2k

Reputation: 370415

Why doesn't the first code snippet work?

Outside of a do block, line breaks don't have any significance. So your first definition of main is equivalent to main = print (isEven 2) print (isOdd 2), which fails because print only takes one argument.

Now you may wonder why we can't just use line breaks to signify that one function should be called after another. The problem with that is that Haskell is (usually) lazy and purely functional, so functions don't have side-effects and there's no meaningful concept of calling one function after another.

So then how does print work at all? print is a function that takes a string and produces a result of type IO (). IO is a type that represents possibly side-effecting operations. main produces a value of this type and the operations described by that value will then be executed. And while there's no meaningful concept of calling one function after another, there is a meaningful concept of executing one IO value's operation after another one's. For this we use the >> operator, which chains two IO values together.

I saw something about "monads" on the internet related to the "do" keyword, does that have something to do with this?

Yes, Monad is a type class (if you don't know what those are yet: they're similar to interfaces in OO languages), which (among others) provides the functions >> and >>=. IO is one instance of that type class (in OO terms: one type that implements that interface), which uses those methods to chain multiple operations after each other.

The do syntax is a more convenient way of using >> and >>=. Specifically your definition of main is equivalent to the following without do:

main = (print (isEven 2)) >> (print (isOdd 2))

(The extra parentheses aren't necessary, but I added them to avoid any confusion about precedence.)

So main produces an IO value that executes the steps of print (isEven 2), followed by those of print (isOdd 2).

Upvotes: 19

Julia Path
Julia Path

Reputation: 2366

I think for the time being you will just have to accept it. Yes, the do-notation is syntactic sugar for the monad type class. Your code could be desugared to the following:

main = print (isEven 2) >> print (isOdd 2)

(>>) means something like do this after that in this particular case. However there is really no good in trying to explain Haskell IO and monads in a StackOverflow answer. Instead I recommend you to just keep learning until your book or whatever you use as a learning resource covers the topic.

Here is however a quick example of what you can do inside of IO-do. Don't bother about the syntax too much.

import System.IO
main = do
  putStr "What's your name? "  -- Print strings
  hFlush stdout                -- Flush output
  name <- getLine              -- Get input and save into variable name
  putStrLn ("Hello " ++ name)
  putStr "What's your age? "
  hFlush stdout
  age <- getLine
  putStr "In one year you will be "
  print (read age + 1)         -- convert from string to other things with read
                               -- use print to print things that are not strings

Upvotes: 5

a3f
a3f

Reputation: 8657

You know that the result of a function should only depend on its input, so let's model print to reflect that:

print :: String -> RealWorld -> (RealWorld, ())

main would then look like this:

main rw0 = let (rw1, _) = print (isEven 2) rw0 in
                          print (isOdd 2) rw1

Now let's define bind f g rw = let (rw', ret) = f rw in g rw' which does this handing through of the RealWorld state and rewrite the snippet to use it:

main = bind (print (isEven 2))
            (print (isOdd 2))

Now let's introduce some syntax sugar that does the binding for us

main = do print (isEven 2)
          print (isOdd 2)

Upvotes: 2

danidiaz
danidiaz

Reputation: 27766

Haskell functions are "pure" and have no notion of sequencing apart from "data dependencies": the result value of a function used as argument to another. At the basic level, there are no statements to be sequenced, only values.

There is a type constructor called IO. It can be applied to other types: IO Int, IO Char, IO String. IO sometype means: "this value is a recipe for doing some stuff in the real world and returning a value of sometype, once the recipe is executed by the runtime".

That's why main has type IO (). You give a recipe for doing stuff in the real world. () is a type with only one value, which provides no information. main is executed only for its effects in the real world.

There are a number of operators for combining IO recipes. A simple one is >> that takes two recipes and returns a recipe for executing the first recipe, then the second. Notice that the combination is done in a pure way, using mere functions, even if the composite recipe actually resembles the sequential statements of imperative programming ("Print this message, then this other message").

To simplify the construction of these "imperative recipes", do-notation was created. It lets you write something resembling the sequential statements of an imperative language, but then it desugars to function applications. Everything you can write in do-notation, you can write (sometimes less clearly) with regular function application.

Upvotes: 3

Related Questions