Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

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.

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.

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top