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?
As a syntactical convenience, do notation does not add anything essential, but it is often preferable for clarity and style. However, do is not needed for a single action, at all. The Haskell "Hello world" is simply: main = putStrLn "Hello world!"
It's merely an infix synonym for fmap , so you can write e.g. Prelude> (*2) <$> [1.. 3] [2,4,6] Prelude> show <$> Just 11 Just "11" Like most infix functions, it is not built-in syntax, just a function definition. But functors are such a fundamental tool that <$> is found pretty much everywhere.
Essentially, a >> b can be read like "do a then do b , and return the result of b ". It's similar to the more common bind operator >>= .
Haskell provides special syntax to support infix notation. An operator is a function that can be applied using infix syntax (Section 3.4), or partially applied using a section (Section 3.5).
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With