Haskell 中的不可变变量是什么意思?

What does immutable variable in Haskell mean?

我对Haskell中不可变变量的概念很困惑。好像我们不能改变Haskell中变量的值。但是当我尝试在 GHCI 中执行以下代码时,变量的值似乎确实发生了变化:

Prelude> foo x=x+1
Prelude> a=1
Prelude> a
1
Prelude> foo a
2
Prelude> a=2
Prelude> a
2
Prelude> foo a
3

这是否与不可变变量的思想冲突?

非常感谢!

Haskell 不允许您修改现有变量。但是,它确实允许您重新使用变量名称,而这就是这里发生的所有事情。一种查看方式是询问 GHCi,使用 :i[nfo] 指令,其中声明了变量:

Prelude> let a = 1
Prelude> :i a
a :: Num a => a     -- Defined at <interactive>:2:5
Prelude> let a = 2
Prelude> :i a
a :: Num a => a     -- Defined at <interactive>:4:5

这实际上是两个完全独立的、不同的变量,只是碰巧叫了同一个名字!如果你只要求 a,较新的定义将是“首选”,但旧的定义仍然存在——正如 chi 在评论中所指出的,一种查看方式是使用 a在函数中:

Prelude> let a = 2
Prelude> :i a
a :: Num a => a     -- Defined at <interactive>:4:5
Prelude> let f x = a + x
Prelude> let a = 3
Prelude> f (-2)
0

f 永远不需要关心您是否定义了一个新变量,它也被称为 a;从它的角度来看,a 是一个始终保持原样的不可变变量。


值得谈谈为什么 GHCi 更喜欢后面的定义。这 而不是 否则会在 Haskell 代码中发生;特别是如果您尝试编译以下模块,它只会给出有关重复定义的错误:

a = 1
a = 2

main :: IO ()
main = print a

在 GHCi 中允许这样的事情的原因是它与 Haskell 模块的工作方式不同。 GHCi 命令的序列实际上形成了 IO monadaction 的序列;即程序必须是

main :: IO ()
main = do
   let a = 1
   let a = 2
   print a

现在,如果你了解过 monad,你就会知道这只是

的语法糖
main =
   let a = 1 in (let a = 2 in (print a))

这确实是您可以重复使用名称 a 的关键所在:第二个 a = 2 生活在 更窄的范围内 比第一个。所以它更本地化,本地定义优先。这是否是个好主意还有待商榷。一个很好的论据是你可以有一个像

这样的函数
greet :: String -> IO ()
greet name = putStrLn $ "Hello, "++name++"!"

它不会因为有人在别处定义而停止工作

name :: Car -> String
name car | rollsOverAtRightTurn car   = "Reliant Robin"
         | fuelConsumption car > 50*litrePer100km
                                      = "Hummer"
         | ...                        = ...

此外,在 GHCi 中“重新定义”变量确实非常有用,即使在适当的程序中重新定义东西不是这样的好主意,这应该表现出一致的行为。


正如 dfeuer 所说,这不是全部事实。你可以在 GHCi 中做一些在 IO do 块中不允许的事情,特别是你可以定义 data 类型和 classes。但是任何普通语句或变量定义都像在 IO monad 中一样。

(使用 GHCi 的另一个答案很好,但需要澄清的是它不是特定于 GHCi 或 monads...)

从下面的Haskell程序可以看出

main =
  let x = 1 in
  let f y = x + y in
  let x = 2 in
  print (x * f 3)

打印8而不是10,变量只能是"bound",不能是"mutated",在Haskell。换句话说,上面的程序等效于(称为α-等效,意味着仅对绑定变量的名称进行一致更改;有关更多详细信息,请参见https://wiki.haskell.org/Alpha_conversion

main =
  let x1 = 1 in
  let f y = x1 + y in
  let x2 = 2 in
  print (x2 + f 3)

显然 x1x2 是不同的变量。