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

yxw*_*yxw 10 haskell functional-programming immutability ghci

我对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
Run Code Online (Sandbox Code Playgroud)

这与不可变变量的想法有冲突吗?

非常感谢!

lef*_*out 20

Haskell不允许您修改现有变量.但是,它确实允许您重复使用变量名称,这就是这里发生的一切.一种看待这种情况的方法是使用:i[nfo]指令询问GHCi ,其中声明了变量:

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
Run Code Online (Sandbox Code Playgroud)

这些实际上是两个完全独立的,不同的变量,恰好被称为同名!如果你只是要求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
Run Code Online (Sandbox Code Playgroud)

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


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

a = 1
a = 2

main :: IO ()
main = print a
Run Code Online (Sandbox Code Playgroud)

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

main :: IO ()
main = do
   let a = 1
   let a = 2
   print a
Run Code Online (Sandbox Code Playgroud)

现在,如果你已经了解了monads,你就会知道这只是语法糖

main =
   let a = 1 in (let a = 2 in (print a))
Run Code Online (Sandbox Code Playgroud)

这是真的,为什么你可以重新使用的名称的关键位a:第二个,a = 2,住在一个较窄的范围比第一.所以它更本地化,本地定义具有优先权.这是一个好主意是否有点值得商榷; 一个很好的论据就是你可以拥有像这样的功能

greet :: String -> IO ()
greet name = putStrLn $ "Hello, "++name++"!"
Run Code Online (Sandbox Code Playgroud)

并且它不会因为有人在其他地方定义而停止工作

name :: Car -> String
name car | rollsOverAtRightTurn car   = "Reliant Robin"
         | fuelConsumption car > 50*litrePer100km
                                      = "Hummer"
         | ...                        = ...
Run Code Online (Sandbox Code Playgroud)

此外,在GHCi中愚弄你可以"重新定义"变量非常有用,即使在适当的程序中重新定义东西并不是一个好主意,这应该表现出一致的行为.


正如dfeuer所说,这不是全部真相.你可以在GHCi中做一些IOdo-block中不允许的事情,特别是你可以定义data类型和classes.但是任何正常的陈述或变量定义都与IOmonad中的一样.