在 haskell 中等于(=) Vs 左箭头(< -)符号

工作守则:

import System
main = do
[file1, file2] <- getArgs
--copy file contents
str <- readFile file1
writeFile file2 str

崩溃代码:

import System
main = do
[file1, file2] = getArgs
str = readFile file1
writeFile file2 str

当我尝试时,它抛出了一个错误:

A.hs: 6:18: 解析输入’=’时的错误

那么,<-=有什么不同呢?

34936 次浏览
do
x <- y
f x

等同于:

y >>= \x -> f x

do
let x = y
f x

相当于

f y

也就是说,let/=没有一元结合,而 <-有。

为了理解真正的区别,你必须理解单子,以及@right old 在他们的回答中描述的去糖化。

对于 IO 单子的特定情况,就像你的 getArgs例子一样,一个粗略但有用的直觉可以这样做:

  • IBC3 IO action,获取其结果,并将其绑定到 x
  • let x = actionx定义为等价于 action,但是不运行任何东西。稍后,您可以使用 y <- x表示 y <- action

来自允许闭包的命令式语言的程序员可能会把这个与 JavaScript 进行粗略的并行比较:

var action = function() { print(3); return 5; }


// roughly equivalent to x <- action
print('test 1')
var x = action()  // output:3
// x is 5


// roughly equivalent to let y = action
print('test 2')
var y = action    // output: nothing
// y is a function


// roughly equivalent to z <- y
print('test 3')
var z = y()       // output:3
// z is 5

同样,这个比较只关注 IO。对于其他单子,您需要检查 >>=实际上是什么,并考虑 do的去糖化。

由于类型不匹配,代码无法编译。让我们加载一个 GHCI 会话并查看您正在使用的函数类型-

> :t writeFile
writeFile :: FilePath -> String -> IO ()
>
> :t readFile
readFile :: FilePath -> IO String

所以 writeFile需要 FilePathString。您希望从 readFile获得 String-但是 readFile返回 IO String而不是 String

Haskell 是一种非常有原则的语言。它区分了 纯洁函数(每次用相同的参数调用它们时都会给出相同的输出)和 不纯洁代码(可能会给出不同的结果,例如,如果函数依赖于某些用户输入)。处理输入/输出(IO)的函数总是有一个返回类型,这个返回类型用 IO标记。类型系统确保您不能在纯函数中使用不纯的 IO代码——例如,函数 readFile返回的不是 String,而是 IO String

这就是 <-符号的重要性所在。它允许您获取 IO内部的 String,并且它确保无论您对该字符串做什么,您定义的函数将始终用 IO标记。比较以下-

> let x = readFile "tmp.txt"
> :t x
x :: IO String

这不是我们想要的

> y <- readFile "tmp.txt"
> :t y
y :: String

这就是我们想要的。如果您曾经有一个返回 IO a的函数,并且您想访问 a,那么您需要使用 <-将结果分配给一个名称。如果你的函数不返回 IO a,或者如果你不想在 IO中的 a,那么你可以使用 =

let x = readFile file1

这将执行动作“ readFile file1”并将 行动存储在 x中。

x <- readFile file1

这个 执行动作“ readFile file1”并将动作的 结果存储在 x中。

在第一个示例中,x是一个未执行的 I/O 操作对象。在第二个示例中,x是磁盘上文件的内容。