在Haskell中使用'let'的功能纯度

ang*_*yen 6 haskell

当我正在学习Haskell时,我理解它是一种纯函数式语言.我无法理解为什么 - let陈述不违反纯度.

例如(在ghci中):

Prelude> let e = exp 1
Prelude> e
2.718281828459045
Prelude> let e = 2
Prelude> e
2
Run Code Online (Sandbox Code Playgroud)

是不是我的第二个let产生副作用的声明?或者是第二个let声明是一个新的关闭?

mel*_*ene 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> 
Run Code Online (Sandbox Code Playgroud)


And*_*ewC 17

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

*Main> (let length = 2 in show length) ++ ' ':show (length "Hello")
"2 5"
Run Code Online (Sandbox Code Playgroud)

这里第一个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")
Run Code Online (Sandbox Code Playgroud)

这是你的例子:

*Main> let e = exp 1 in show e ++ " " ++ let e = 2 in show e
"2.718281828459045 2"
Run Code Online (Sandbox Code Playgroud)

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

*Main> let e = exp 1 in (show e ++ " " ++ (let e = 2 in (show e)))
"2.718281828459045 2"
Run Code Online (Sandbox Code Playgroud)

第一个e是隐藏而不是编辑.参考透明度得以保留,但这绝对是不好的做法,因为它很难遵循.


现在秘密地说,交互式提示有点像doIO monad中的一个大块,所以让我们看一下:

testdo = do
  let e = exp 1
  print e
  let e = 2
  print e
Run Code Online (Sandbox Code Playgroud)

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

testWrite = do
   writeFile "test.txt" "Hello Mum"
   xs <- readFile "test.txt"
   print xs
   writeFile "test.txt" "Yo all"
   xs <- readFile "test.txt"
   print xs
Run Code Online (Sandbox Code Playgroud)

现在我们在什么意义上得到了参考透明度?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))
Run Code Online (Sandbox Code Playgroud)

现在更清楚的是,看起来像赋值的只是局部范围.你可能很乐意这么做

increment :: [Int] -> [Int]
increment = \x -> map (\x -> x+1) x
Run Code Online (Sandbox Code Playgroud)

这是做同样的事情.


总结
似乎是赋值只是引入了一个新的局部范围.唷.如果你经常使用它,你会很清楚你的代码意味着什么.

  • 请注意,问题在do-notation上下文中使用`let`,即`do {...; 设e = 1; ...}`而不是在'let ... in ...'语境中.推理几乎完全相同,但值得一提. (6认同)