What does Haskell's "do" keyword do?
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)
.
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
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.