mares
mares

Reputation: 23

Haskell write String, easy question

writeStr []=putChar ' '         
writeStr (x:xs) = (putChar x)(writeStr xs)

Hello, and thanks in advance, i get a type error, it should be a simple answer but i just dont get where the error is coming from.

Upvotes: 2

Views: 502

Answers (2)

Paul Johnson
Paul Johnson

Reputation: 17786

FUZxxl has answered the immediate question, but I'd like to expand on it with some more ways of writing "writeStr" to illustrate more about monads.

As delnan said in the comments, you can also write

writeStr [] = return ()
writeStr (x:xs) = putChar x >> writeStr xs

This is actually the "desugared" version of the "do" notation. The ">>" operator is used to daisy-chain monadic actions together. Its actually a specialised version of the "bind" operator, written ">>=". See this question for more details.

But when you look at this, it seems that all we are doing is applying "putChar" to each element in the argument list. There is already a function in the Prelude called "map" for doing this, so maybe we could write:

writeStr xs = map putChar xs

But when you try that it won't work. The reason becomes evident if you go into GHCi and type this:

:type map putChar "Hello"
[IO ()]

You want a single "IO()" action, but this gives you a list of them. What you need is a function that turns this list of IO actions into a single IO action. Fortunately one exists. The Prelude contains these two functions

sequence :: [IO a] -> IO [a]
sequence_ :: [IO a] -> IO ()

The first one is for when you want the list of results, the second is for cases where you don't, like this one. (Throughout this answer I'm going to be giving the IO-specific type signatures for clarity, but its important to remember that all these functions actually work for any monad.)

So now you can write:

writeStr xs = sequence_ $ map putChar xs

But there is a way of shortening this. Recall the "." operator, which sticks two functions together, and the way Haskell has of "currying" function arguments? We can rewrite the function above as:

writeStr = sequence_ . map putChar

This "point-free" style looks and feels very odd at first; it makes "writeStr" look more like a constant than a function. But it avoids the need to track variable names around the code when you are reading it, and so is often preferred. Its also a lot shorter and more readable when you are putting something complicated as the argument to "map" or similar higher order functions.

But we can go even shorter. The "sequence . map f" pattern is very common, so the "Control.Monad" module defines a couple more functions to embody it:

mapM :: (a -> IO b) -> [a] -> IO [b]
mapM f = sequence . map f

mapM_ :: (a -> IO b) -> [a] -> IO ()
mapM_ f = sequence_ . map f

So you can finally write

writeStr = mapM_ putChar

Upvotes: 3

fuz
fuz

Reputation: 92986

Your code is a bit strange. If I got it right, you try to print a string. Your method is to put the first string, and than the second. But it's not possible in Haskell to combine two IO actions like this. Have a look in your tutorial again about this, here's hwo it could look like:

writeStr []     = return () -- you had putChar ' ',
writeStr (x:xs) = do putChar x -- but this would print a superfluous whtiespace
                     writeStr xs

If you want to do several things sequentially, either use the do-keyword or monadic combinators. Its very easy, just like this:

do action1
   action2
   action3
   ...

Upvotes: 4

Related Questions