Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell beginner, trying to output a list

Tags:

haskell

I suppose everyone here already has seen one of these (or at least a similar) question, still I need to ask because I couldn't find the answer to this question anywhere (mostly because I don't know what exactly I should be looking for)

I wrote this tiny script, in which printTriangle is supposed to print out the pascal triangle.

fac = product . enumFromTo 2

binomial n k  = (product (drop (k-1) [2..n])) `div` (fac (n-k))

pascalTriangle maxRow = 
               do row<-[0..maxRow-1]
                  return (binomialRow row)
                  where
                  binomialRow row = 
                              do k<-[0..row]
                                 return (binomial row k)

printTriangle :: Int -> IO ()
printTriangle rows  = do row<-(triangle)
                         putStrLn (show row)
                         where 
                         triangle = pascalTriangle rows

Now for reasons that are probably obvious to the trained eye, but completely shrouded in mystery for me, i get the following error when trying to load this in ghci:

   Couldn't match expected type `IO t0' with actual type `[[Int]]'
    In a stmt of a 'do' expression: row <- (triangle)
    In the expression:
      do { row <- (triangle);
           putStrLn (show row) }
    In 
an equation for `printTriangle':
            printTriangle rows
              = do { row <- (triangle);
                     putStrLn (show row) }
              where
                  triangle = pascalTriangle rows

what im trying to do is something like I call printTriangle like this:

printTriangle 3

and I get this output:

[1]
[1,1]
[1,2,1]

If anyone could explain to me why what I'm doing here doesn't work (to be honest, I am not TOO sure what exactly I am doing here; I am used to imperative languages and this whole functional programming thingy is still mighty confusing to me), and how I could do it in a less dumb fashion that would be great.

Thanks in advance.

like image 731
Cubic Avatar asked Nov 11 '11 22:11

Cubic


2 Answers

You said in a comment that you thought lists were monads, but now you're not sure -- well, you're right, lists are monads! So then why doesn't your code work?

Well, because IO is also a monad. So when the compiler sees printTriangle :: Int -> IO (), and then do-notation, it says "Aha! I know what to do! He's using the IO monad!" Try to imagine its shock and dispair when it discovers that instead of IO monads, it finds list monads inside!

So that's the problem: to print, and deal with the outside world, you need to use the IO monad; inside the function, you're trying to use lists as the monad.

Let's see how this is a problem. do-notation is Haskell's syntactic sugar to lure us into its cake house and eat us .... I mean it's syntactic sugar for >>= (pronounced bind) to lure us into using monads (and enjoying it). So let's write printTriangle using bind:

printTriangle rows = (pascalTriangle rows) >>= (\row -> 
                     putStrLn $ show row)

Okay, that was straightforward. Now do we see any problems? Well, lets look at the types. What's the type of bind? Hoogle says: (>>=) :: Monad m => m a -> (a -> m b) -> m b. Okay, thanks Hoogle. So basically, bind wants a monad type wrapping a type a personality, a function that turns a type a personality into (the same) monad type wrapping a type-b personality, and ends up with (the same) monad type wrapping a type-b personality.

So in our printTriangle, what do we have?

  • pascalTriangle rows :: [[Int]] -- so our monad is [], and the personality is [Int]
  • (\row -> putStrLn $ show row) :: [Int] -> IO () -- here the monad is IO, and the personality is ()

Well, crap. Hoogle was very clear when it told us that we had to match our monad types, and instead, we've given >>= a list monad, and a function that produces an IO monad. This makes Haskell behave like a little kid: it closes its eyes and stomps on the floor screaming "No! No! No!" and won't even look at your program, much less compile it.


So how do we appease Haskell? Well, others have already mentioned mapM_. And adding explicit type signatures to top-level functions is also a good idea -- it can sometimes help you to get compile errors sooner, rather than later (and get them you will; this is Haskell after all :) ), which makes it much much easier to understand the error messages.

I'd suggest writing a function that turns your [[Int]] into a string, and then printing the string out separately. By separating the conversion into a string from the IO-nastiness, this will allow you to get on with learning Haskell and not have to worry about mapM_ & friends until you're good and ready.

showTriangle :: [[Int]] -> String
showTriangle triangle = concatMap (\line -> show line ++ "\n") triangle

or

showTriangle = concatMap (\line -> show line ++ "\n")

Then printTriangle is a lot easier:

printTriangle :: Int -> IO ()
printTriangle rows = putStrLn (showTriangle $ pascalTriangle rows)

or

printTriangle = putStrLn . showTriangle . pascalTriangle
like image 53
Matt Fenwick Avatar answered Nov 05 '22 11:11

Matt Fenwick


If you want to print elements of a list on new lines you shall see this question.

So

printTriangle rows  = mapM_ print $ pascalTriangle rows

And

λ> printTriangle 3
[1]
[1,1]
[1,2,1]

Finally, what you're asking for is seems to be mapM_.

like image 32
ДМИТРИЙ МАЛИКОВ Avatar answered Nov 05 '22 13:11

ДМИТРИЙ МАЛИКОВ