Maciej Goszczycki
Maciej Goszczycki

Reputation: 1128

Haskell type error message

For the past couple of days I've been trying to learn Haskell. While I'm slowly getting better but I'm finding it hard to reason with Haskell's IO, probably due to my lack of knowledge. I've been trying to write a simple todo list program. Here's what I've got:

tadd todo = do
    td <- getLine
    td:todo

tdel todo = do
    trem <- getLine
    let rid = read trem :: Int
    [todo !! x | x <- [0..(length todo-1)], not $ x == rid]

tls todo = do
    mapM putStrLn [ (show x) ++ (todo !! x) | x <- [0..(length todo -1)] ]
    todo

mtodo "add" todo = tadd todo
mtodo "del" todo = tdel todo
mtodo "ls"  todo = tls todo


bege = do
    com <- getLine
    mtodo com []

main = bege

I was excepting the mtodo to be mtodo :: [IO String] -> [IO String] -> [IO String] and tadd, tdel, tls to be :: [IO String] -> [IO String].

Instead I just get this scary error messsage

[1 of 1] Compiling Main             ( todo.hs, todo.o )

todo.hs:3:9:
    Couldn't match type `[]' with `IO'
    Expected type: IO String
      Actual type: [String]
    In a stmt of a 'do' block: td : todo
    In the expression:
      do { td <- getLine;
           td : todo }
    In an equation for `tadd':
        tadd todo
          = do { td <- getLine;
                 td : todo }

todo.hs:8:9:
    Couldn't match expected type `IO' with actual type `[]'
    In a stmt of a 'do' block:
      [todo !! x | x <- [0 .. (length todo - 1)], not $ x == rid]
    In the expression:
      do { trem <- getLine;
           let rid = ...;
           [todo !! x | x <- [0 .. (length todo - 1)], not $ x == rid] }
    In an equation for `tdel':
        tdel todo
          = do { trem <- getLine;
                 let rid = ...;
                 [todo !! x | x <- [0 .. (length todo - 1)], not $ x == rid] }

todo.hs:12:9:
    Couldn't match type `[]' with `IO'
    Expected type: IO [Char]
      Actual type: [[Char]]
    In a stmt of a 'do' block: todo
    In the expression:
      do { mapM
             putStrLn [(show x) ++ (todo !! x) | x <- [0 .. (length todo - 1)]];
           todo }
    In an equation for `tls':
        tls todo
          = do { mapM
                   putStrLn [(show x) ++ (todo !! x) | x <- [0 .. (length todo - 1)]];
                 todo }

Any ideas what's wrong with my types? (Also - is there anything I should change?). Thanks

Upvotes: 2

Views: 883

Answers (2)

C. A. McCann
C. A. McCann

Reputation: 77374

Looking at this code:

tadd todo = do
    td <- getLine
    td:todo

The problem is that you have td:todo as a line by itself; you're using getLine, so the whole block should be using IO, but your last line is a list. This is what the "can't match" errors are about--the code seems to say that IO and [] should be the same thing, but of course they aren't.

To lift a value into the monadic context in order to use it as the result value of the do block, use the function return:

tadd todo = do
    td <- getLine
    return $ td:todo

This will give tadd the type [String] -> IO [String]. This isn't what you seem to expect the type to be, but from what you've written I don't think you want to be using [IO String].

The same applies to the list comprehension in tdel and the final todo in tls.

Also, your expected type here is off:

mtodo :: [IO String] -> [IO String] -> [IO String]
mtodo "add" todo = tadd todo
mtodo "del" todo = tdel todo
mtodo "ls"  todo = tls todo

The first argument is clearly a String here, so based on the above you probably want mtodo :: String -> [String] -> IO [String]

This is probably also not what you want:

bege = do
    com <- getLine
    mtodo com []

A few other notes:

Based on the rest of the code you seem to want an interactive program, but this will run it only once. What you probably want instead is something like this:

bege todo = do
    com <- getLine
    todo' <- mtodo com todo
    bege todo'

main = bege []

Your list comprehension [todo !! x | x <- [0..(length todo-1)], not $ x == rid] is very inefficient and not idiomatic at all. As a rule of thumb, if you're still learning Haskell you should probably never use (!!) unless you're discarding the list afterward. Haskell lists are linear sequences, so indexing requires traversing everything up to that point. Not really the best data structure for what you're doing, but you should at least find some way to do that that doesn't require traversing the list multiple times.

As an example of avoiding (!!), you could rewrite mapM putStrLn [ (show x) ++ (todo !! x) | x <- [0..(length todo -1)] ] as mapM_ putStrLn $ zipWith (\n t -> show n ++ t) [0..] todo, which avoids all the confusing and superfluous stuff like indexing and worrying about the length of the list, as well as traversing the list only once, instead of traversing it first to compute the length, then partially traversing it up to each item to print it.

This is more a matter of taste, but instead of writing this:

mtodo "add" todo = tadd todo
mtodo "del" todo = tdel todo
mtodo "ls"  todo = tls todo

You could abbreviate it as:

mtodo "add" = tadd
mtodo "del" = tdel
mtodo "ls"  = tls

...which means exactly the same thing, and is arguably clearer by reducing excess noise and making it obvious that all it's doing is selecting one of several other functions based on the string given.

Also, the let rid = read trem :: Int bit is unnecessary--just use the function readLn instead of getLine, which does exactly what you want here. Also, the type annotation is probably unnecessary (and distracting) because the type should be inferred based on what you do with the result.

Upvotes: 10

Dirk Holsopple
Dirk Holsopple

Reputation: 8831

You need to learn about return. All you need to do to get this to compile is to add return to the last line of tadd, tdel and tls.

You'll get better error messages if you use explicit type signatures.

tadd :: [String] -> IO [String]
tadd todo = do
  td <- getLine
  return (td:todo)

In this case, (td:todo) has type [String], you need to add return to bring it into the IO monad, which is what the function needs to return. When you use do notation, each line needs to have a type in the monad you're in (except for the ones starting with let), in this case that's IO.

tdel and tls have exactly the same problem.

tdel :: [String] -> IO [String]
tdel todo = do
  trem <- getLine
  let rid = read trem :: Int
  return [todo !! x | x <- [0..(length todo-1)], not $ x == rid]

tls :: [String] -> IO [String]
tls todo = do
  mapM putStrLn [ (show x) ++ (todo !! x) | x <- [0..(length todo -1)] ]
  return todo

Upvotes: 1

Related Questions