如果您熟悉 C,请考虑声明变量和分配值之间的区别。例如,您可以自己声明一个变量,然后再分配给它:
int i;
i = 7;
或者你可以同时声明一个变量并赋初值:
int i = 7;
在任何一种情况下,您都可以通过在第一次初始化或赋值后再次赋值来改变变量的值:
int i = 7; // Declaration and initial assignment
i = 5; // Mutation
Haskell 中的赋值只像第二个例子一样工作——带有初始化的声明:
- 你声明了一个变量;
- Haskell 不允许未初始化的变量,因此您需要在声明中提供值;
- 没有突变,因此声明中给出的值将是该变量在其 scope 中的唯一值。
我用粗体和超链接“范围”,因为它是这里的第二个关键组成部分。这是您的问题之一:
“removeLower”是不可变的?即使它是一个函数对象?但我仍然可以使用“let”为这个名称分配其他东西。
将removeLower 绑定到您在示例中定义的函数后,名称removeLower 将始终引用该函数在该定义的范围内。这很容易在解释器中演示。首先,我们定义一个函数foo:
Prelude> let foo x = x + 2
Prelude> foo 4
6
现在我们定义一个使用foo的bar:
Prelude> let bar x = foo (foo x)
Prelude> bar 4
8
现在我们将foo“重新定义”为不同的东西:
Prelude> let foo x = x + 3
Prelude> foo 4
7
现在你认为bar 会发生什么?
Prelude> bar 4
8
还是一样!因为foo 的“重新定义”并没有改变任何东西——它只是说,在“重新定义”创建的新范围内,名称foo代表加三的函数。 bar 的定义是在 foo x = x + 2 的较早范围内进行的,因此这就是名称 foo 在 bar 的定义中的含义。 foo 的原始值没有被“重新定义”破坏或改变。
在 Haskell 程序中和在 C 程序中一样,相同的名称仍然可以在程序的不同范围内引用不同的值。这就是使“变量”可变的原因。不同之处在于,在 Haskell 中,您永远不能在一个范围内改变变量的值。但是,您可以shadow 定义——变量的使用在某种意义上指的是该名称的“最近”定义。 (对于解释器,该变量的最新let 声明。)
现在,除此之外,这里是 Haskell 中存在的用于变量绑定(“赋值”)的语法。首先,模块中有顶级声明:
module MyLibrary (addTwo) where
addTwo :: Int -> Int
addTwo x = x + 2
这里名称addTwo 以给定函数作为其值声明。顶级声明可以在 where 块中包含私有的辅助声明:
addSquares :: Integer -> Integer
addSquares x y = squareOfX + squareOfY
where square z = z * z
squareOfX = square x
squareOfY = square y
然后是let ... in ... 表达式,它允许您为任何表达式声明一个局部变量:
addSquares :: Integer -> Integer
addSquares x y =
let square z = z * z
squareOfX = square x
squareOfY = square y
in squareOfX + squareOfY
然后是do-notation,它有自己的声明变量的语法:
example :: IO ()
example = do
putStrLn "Enter your first name:"
firstName <- getLine
putStrLn "Enter your lasst name:"
lastName <- getLine
let fullName = firstName ++ " " ++ lastName
putStrLn ("Hello, " ++ fullName ++ "!")
var <- action 分配一个由动作 产生的值(例如,从标准输入读取一行),而let var = expr 分配一个由函数产生的值(例如,连接一些字符串)。请注意,do 块中的 let 与上面的 let ... in ... 不同!
最后,在列表推导中,您会得到与 do-notation 中相同的赋值语法。