6

当我正在学习 Haskell 时,我知道它是一种纯粹的函数式语言。我无法理解为什么let- 语句不违反纯度。

例如(在 ghci 中):

Prelude> let e = exp 1
Prelude> e
2.718281828459045
Prelude> let e = 2
Prelude> e
2

我的第二个let陈述不是产生副作用吗?或者第二个let语句是一个新的闭包?

4

2 回答 2

22

你的第二个let创建一个新的绑定,e它会隐藏现有的变量。它不修改e. 您可以通过以下方式轻松检查:

Prelude> let e = 1
Prelude> let f () = "e is now " ++ show e
Prelude> f ()
"e is now 1"
Prelude> let e = 2
Prelude> e
2
Prelude> f ()
"e is now 1"
Prelude> 
于 2012-11-24T21:06:29.800 回答
17

let引入了一个具有单个不可更改值的新局部变量,并且它具有比任何周围定义更多的局部范围,例如:

*Main> (let length = 2 in show length) ++ ' ':show (length "Hello")
"2 5"

这里第一个length的值为 2,但它的范围在括号内。在括号之外,length表示它一直以来的意思。没有编辑任何内容,只是引入了一个更局部的变量,它恰好与不同范围内的另一个变量具有相同的名称。让我们通过省略括号并让它尝试生成length一个数字和一个函数来让 ghci 发疯:

*Main> let length = 2 in show length ++ ' ':show (length "Hello")

<interactive>:1:14:
    No instance for (Num ([Char] -> a0))
      arising from the literal `2'
    Possible fix: add an instance declaration for (Num ([Char] -> a0))
    In the expression: 2
    In an equation for `length': length = 2
    In the expression:
      let length = 2 in show length ++ ' ' : show (length "Hello")

<interactive>:1:19:
    No instance for (Show ([Char] -> a0))

      arising from a use of `show'
    Possible fix: add an instance declaration for (Show ([Char] -> a0))
    In the first argument of `(++)', namely `show length'
    In the expression: show length ++ ' ' : show (length "Hello")
    In the expression:
      let length = 2 in show length ++ ' ' : show (length "Hello")

这是你的例子:

*Main> let e = exp 1 in show e ++ " " ++ let e = 2 in show e
"2.718281828459045 2"

我将添加括号以强调范围:

*Main> let e = exp 1 in (show e ++ " " ++ (let e = 2 in (show e)))
"2.718281828459045 2"

第一个e是隐藏而不是编辑。保留了引用透明度,但这绝对是不好的做法,因为它很难遵循。


现在偷偷的交互式提示有点像doIO monad 中的一大块,我们来看看:

testdo = do
  let e = exp 1
  print e
  let e = 2
  print e

现在我不得不承认这看起来很像破坏参照透明性,但请记住,这看起来也是如此:

testWrite = do
   writeFile "test.txt" "Hello Mum"
   xs <- readFile "test.txt"
   print xs
   writeFile "test.txt" "Yo all"
   xs <- readFile "test.txt"
   print xs

现在,我们在什么意义上获得了参照透明性?xs显然是指两个不同的字符串。那么,这个do符号实际上是什么意思?它是语法糖

testWrite = writeFile "test.txt" "Hello Mum"
         >> readFile "test.txt" 
         >>= (\xs -> print xs 
         >> writeFile "test.txt" "Yo all"
         >> readFile "test.txt"
         >>= (\xs -> print xs))

现在更清楚的是,看起来像是赋值的东西又只是局部范围。你大概很乐意做

increment :: [Int] -> [Int]
increment = \x -> map (\x -> x+1) x

这是做同样的事情。


总结
似乎是分配只是引入了一个新的本地范围。呸。如果你经常使用它,你就会很不清楚你的代码是什么意思。

于 2012-11-24T21:21:10.607 回答