在过去的几天里,我一直在努力学习Haskell。虽然我正在慢慢变好,但我发现很难用Haskell的IO来推理,可能是因为我缺乏知识。我一直在尝试编写一个简单的待办事项列表程序。这就是我所拥有的:
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
我除了mtodo mtodo :: [IO String] -> [IO String] -> [IO String]
和tadd,tdel,tls为:: [IO String] -> [IO String]
。
相反,我只是得到了这个可怕的错误消息
[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 }
任何想法我的类型有什么问题? (另外 - 有什么我应该改变的吗?)。感谢
答案 0 :(得分:10)
查看此代码:
tadd todo = do
td <- getLine
td:todo
问题是你有td:todo
作为一条线;您正在使用getLine
,因此整个块应该使用IO
,但您的最后一行是一个列表。这就是“无法匹配”错误的含义 - 代码似乎说IO
和[]
应该是相同的,但当然不是。
要将值提升到monadic上下文中以便将其用作do
块的结果值,请使用函数return
:
tadd todo = do
td <- getLine
return $ td:todo
这将为tadd
提供[String] -> IO [String]
类型。这不是您期望的类型,但是根据您的编写,我认为您不想使用[IO String]
。
同样适用于tdel
中的列表理解和todo
中的最终tls
。
此外,您的预期类型已关闭:
mtodo :: [IO String] -> [IO String] -> [IO String]
mtodo "add" todo = tadd todo
mtodo "del" todo = tdel todo
mtodo "ls" todo = tls todo
第一个参数显然是String
,所以基于上面你可能想要mtodo :: String -> [String] -> IO [String]
这可能也不是你想要的:
bege = do
com <- getLine
mtodo com []
其他几点说明:
基于其他代码,您似乎想要一个交互式程序,但这只会运行一次。你可能想要的是这样的东西:
bege todo = do
com <- getLine
todo' <- mtodo com todo
bege todo'
main = bege []
您的列表理解[todo !! x | x <- [0..(length todo-1)], not $ x == rid]
非常低效,而且根本不是惯用语。根据经验,如果你还在学习Haskell,你可能永远不会使用(!!)
,除非你之后丢弃了这个列表。 Haskell列表是线性序列,因此索引需要遍历到那一点的所有内容。对于你正在做的事情来说,这不是最好的数据结构,但你至少应该找到一些不需要多次遍历列表的方法。
作为避免(!!)
的示例,您可以将mapM putStrLn [ (show x) ++ (todo !! x) | x <- [0..(length todo -1)] ]
重写为mapM_ putStrLn $ zipWith (\n t -> show n ++ t) [0..] todo
,这可以避免所有令人困惑和多余的内容,例如索引和担心列表的长度,以及因为只遍历列表一次,而不是先遍历它来计算长度,然后将其部分遍历到每个项目进行打印。
这更多的是品味问题,而不是写下来:
mtodo "add" todo = tadd todo
mtodo "del" todo = tdel todo
mtodo "ls" todo = tls todo
您可以将其缩写为:
mtodo "add" = tadd
mtodo "del" = tdel
mtodo "ls" = tls
......这意味着完全相同的事情,并且可以通过减少过多的噪音并且明显地表明所有它正在根据给定的字符串选择其他几个函数中的一个来说更清楚。
此外,let rid = read trem :: Int
位是不必要的 - 只需使用函数readLn
而不是getLine
,这完全符合您的要求。此外,类型注释可能是不必要的(并且分散注意力),因为应该根据您对结果的处理来推断类型。
答案 1 :(得分:1)
你需要了解回归。要使其编译,您需要做的就是将返回添加到tadd,tdel和tls的最后一行。
如果使用显式类型签名,您将获得更好的错误消息。
tadd :: [String] -> IO [String]
tadd todo = do
td <- getLine
return (td:todo)
在这种情况下,(td:todo)
具有类型[String]
,您需要添加return以将其带入IO monad,这是函数需要返回的内容。当你使用do notation时,每一行都需要在你所在的monad中有一个类型(除了以let开头的那个),在这种情况下是IO。
tdel和tls有完全相同的问题。
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