我对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
这与不可变变量的想法有冲突吗?
非常感谢!
答案 0 :(得分:19)
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
这些实际上是两个完全独立的,不同的变量,恰好被称为同名!如果你只是要求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 monad中的动作序列 †;即该计划必须是
main :: IO ()
main = do
let a = 1
let a = 2
print a
现在,如果你已经了解了monads,你就会知道这只是语法糖
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-block中不允许的事情,特别是你可以定义data
类型和class
es。但任何正常的陈述或变量定义都与IO
monad中的行为一样。
答案 1 :(得分:0)
(使用GHCi的另一个答案很好,但澄清它不是GHCi或monad特有的......)
从以下Haskell程序
的事实可以看出main =
let x = 1 in
let f y = x + y in
let x = 2 in
print (x * f 3)
打印8
而不是10
,变量只能在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)
很明显,x1
和x2
是不同的变量。