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中的一样.