vuzun
vuzun

Reputation: 952

Issue with Haskell's "do"

I wrote a Haskell function that calculates the factorial of every number in a given list and prints it to the screen.

factPrint list =
if null list
    then putStrLn ""
    else do putStrLn ((show.fact.head) list)
        factPrint (tail list)

The function works, but I find the third line a bit confusing. Why hasn't the compiler(GHC) reported an error on it since there is no "do" before the "putStrLn" (quasi?)function? If I omit "do" from the 4th line, an error pops up as expected.

I'm quite new to Haskell and its ways, so please pardon me if I said something overly silly.

Upvotes: 4

Views: 429

Answers (4)

Nathan Shively-Sanders
Nathan Shively-Sanders

Reputation: 18389

If you are new to Haskell, think of do as similar to required braces after if in a C-like language:

if (condition)
  printf("a"); // braces not required
else {
  printf("b"); // braces required
  finish();
}

do works the same way in Haskell.


Maybe it would help to look at the type of factPrint, and then refactor to use pattern matching:

factPrint :: [Int] -> IO ()
factPrint [] = putStrLn ""
factPrint list = do
  putStrLn (show.fact.head) list
  factPrint (tail list)

So, if factPrint returns IO (), and the type of putStrLn "" is IO (), then it's perfectly legal for factPrint [] to equal putStrLn "". No do required--in fact, you could just say factPrint [] = return () if you didn't want the trailing newline.

Upvotes: 5

RobV
RobV

Reputation: 28655

The do keyword is used for sequencing, an if-then-else in Haskell doesn't have to contain a do at all if each branch is a single statement eg.

if a
  then b
  else c

You need the do in your example as you are sequencing two operations on your else branch. If you omit the do then the factPrint(tail list) statement is considered to not be part of the function and thus the compiler complains as it's encountered an unexpected statement.

Upvotes: 3

sepp2k
sepp2k

Reputation: 370415

do is used to tie multiple monadic expressions together. It has no effect when followed by only a single expression.

For the if to be well-formed it is only necessary that the then-clause and the else-clause have the same type. Since both clauses have the type IO () this is the case.

Upvotes: 4

newacct
newacct

Reputation: 122499

do putStrLn ((show.fact.head) list)
   factPrint (tail list)

is actually another way of writing

putStrLn ((show.fact.head) list) >> factPrint (tail list)

which, in turn, means

putStrLn ((show.fact.head) list) >>= \_ -> factPrint (tail list)

The do notation is a convenient way of stringing these monads together, without this other ugly syntax.

If you only have one statement inside the do, then you are not stringing anything together, and the do is redundant.

Upvotes: 11

Related Questions