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
类型和 class
es。但是任何普通语句或变量定义都像在 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)
显然 x1
和 x2
是不同的变量。
我对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
类型和 class
es。但是任何普通语句或变量定义都像在 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)
显然 x1
和 x2
是不同的变量。