一个类型为 IO String -> String 的 Haskell 函数。

38

我用 Haskell 写了一些代码来创建文本索引。顶层函数看起来像这样:

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

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

index readFile "input.txt"

这行代码是无法工作的,因为readFile的类型为 FilePath -> IO String。

期望的类型是'String', 而推断出的类型是 'IO String'

我看到了错误,但我找不到任何类型为:

IO String -> String

我猜成功的关键在于某些单子上,但我找不到解决问题的方法。


3
这里有一个不错的单子教程:http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html。 - R. Martinho Fernandes
1
在SO中可以找到其他很好的资源。只需查看屏幕右下方的相关部分即可。 - R. Martinho Fernandes
这里还有一个:https://dev59.com/rVrUa4cB1Zd3GeqPkoaX - atravers
4个回答

44

你可以很容易地编写一个函数来调用readFile操作,并将结果传递给你的index函数。

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

然而,使用IO单子的一切都会受到影响,因此此函数的类型为:

readAndIndex :: FilePath -> IO [(String, [Integer])]

8
只是一个简短的句子:“IO模拟器会影响到所有使用它的部分。” 我原本就认为是这样,现在看到得到了确认,感觉很好。谢谢^^ - drumfire

29

没有这样的函数是有非常好的理由的。

Haskell有函数纯度的概念。这意味着当使用相同参数调用函数时,函数总是返回相同的结果。唯一允许使用IO的地方是在IO单子(monad)中。

如果存在这样的函数

index :: IO String -> String

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

then we could suddenly do IO actions anywhere by calling, for example:


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

函数的纯度是一个非常有用的特性,我们不希望失去它,因为它允许编译器更加自由地重新排序和内联函数,而不改变语义。它还可以使函数在不同核心之间启动,同时也给程序员一种安全感,因为如果你可以从类型中知道一个函数能做什么和不能做什么。

* 实际上,确实有这样一个函数。它被称为unsafePerformIO,而且有非常非常好的理由。除非你对自己要做的事情非常确定,否则不要使用它!


14
我会毫不犹豫地说“除非你对自己在做什么200%确定,否则不要使用它”,甚至更简单的说法是“不要使用”。 - R. Martinho Fernandes
6
我认为unsafePerformIO最好的用法是调用某个进程,该进程应始终返回相同的结果。例如,标准库不支持的系统计算。 - alternative
5
补充一下我之前在SO上浏览时发现的旧评论:对于外部函数接口也很有用。 - alternative

16

你不能去掉 IO String 中的 IO monad 部分。这意味着你必须让函数返回 IO [(String, [Integer])]

我建议学习更多关于monads的知识,但是现在你可以使用 liftM 函数:

liftM index (readFile "input.txt")

liftM 的函数签名如下:

liftM :: Monad m => (a -> b) -> m a -> m b
它将一个非单子函数转换为单子函数。

9
fmap index $ readFile "input.txt"

或者

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

您可能需要了解单子和函数子。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接