38

我在 Haskell 中编写了一堆代码来创建文本索引。顶部函数如下所示:

index :: String -> [(String, [Integer])]
index a = [...]

现在我想给这个函数一个从文件中读取的字符串:

index readFile "input.txt"

这不起作用,因为 readFile 的类型是 FilePath -> IO String。

无法将预期类型“字符串”与推断类型“IO 字符串”匹配

我看到了错误,但找不到任何类型的函数:

IO String -> String

我想成功的关键在于某些 Monads,但我找不到解决问题的方法。

4

4 回答 4

43

您可以轻松地编写一个调用 readFile 操作的函数,并将结果传递给您的索引函数。

readAndIndex fileName = do
    text <- readFile fileName
    return $ index text

然而,IO monad 会污染所有使用它的东西,所以这个函数的类型是:

readAndIndex :: FilePath -> IO [(String, [Integer])]
于 2009-11-04T18:13:40.123 回答
29

没有这样的功能是有充分理由的。

Haskell 有函数纯度的概念。这意味着当使用相同的参数调用时,函数将始终返回相同的结果。唯一允许 IO的地方是 IO monad。

如果有*一个函数

index :: IO String -> String

然后我们可以突然通过调用在任何地方执行 IO 操作,例如:

index (launchMissiles >> deleteRoot >> return "PWNd!")

函数纯度是我们不想失去的一个非常有用的特性,因为它允许编译器更自由地重新排序和内联函数,它们可以在不改变语义的情况下被激发到不同的内核,它也给程序员一种感觉安全性,因为如果您可以从函数的类型中知道函数可以做什么和不能做什么。

* 其实有这样的功能。它被称为unsafePerformIO并且它被称为有非常非常好的理由。除非您 100% 确定自己在做什么,否则不要使用它!

于 2009-11-04T19:29:24.743 回答
16

好吧,您无法摆脱IO. IO String这意味着你必须让你的函数 return IO [(String, [Integer])]

我建议学习更多关于 monad 的知识,但现在你可以摆脱这个liftM函数:

liftM index (readFile "input.txt")

liftM有这个签名:

liftM :: Monad m => (a -> b) -> m a -> m b

它采用非单子函数并将其转换为单子函数。

于 2009-11-04T17:31:46.773 回答
9
fmap index $ readFile "input.txt"

或者

readFile "input.txt" >>= return . index

你可能想看看 monad 和 functors

于 2009-11-05T03:20:55.983 回答