在 Haskell 中更新外部变量

3
有一种感觉是Haskell是一种纯函数语言,当然,惯用的代码尽可能地函数化。同时,Haskell确实提供了对其他语言中熟悉的命令式模式进行相当直接的转换的支持,例如http://learnyouahaskell.com/a-fistful-of-monads#do-notation (我知道在某种意义上do-notation仍然是函数式的;这里的重点是它允许对某些命令式设计模式进行相当直接的转换。)
我感兴趣的一个模式是一个函数需要更新外部变量,即存在于与其他代码共享的外部作用域中的变量。这可以在Python中简单地演示:
def three():
    n = 0

    def inc():
        nonlocal n
        n += 1

    inc()
    inc()
    inc()
    return n

可能使用do-notation的某种变体或其他方式,在Haskell中实现上述设计模式吗?如果可以,如何实现?
明确这个问题的范围:
我不是在问在Haskell中解决上述问题的最佳方法。显然,答案是three = 3。这只是一个例子。
我不是在问上述设计模式是好还是坏。显然,那将是一个观点问题。
我不是在问在编写Haskell时,应该尽力避免使用命令式设计模式。显然,这也将是一个观点问题。
我只是在问是否可以在Haskell中实现上述设计模式,如果可以,如何实现。

3
你的答案很好。为了让你的思路更清晰,我建议你在 Haskell 中表示“可修改”的时候避免使用“变量”这个词。一个好的通用词语是“引用单元”。在 Haskell 中有许多类型的引用单元,例如IORefSTRef以及(名字容易令人混淆的)MVarTVar。即使是被命名为TVarMVar的这些对象,在 Haskell 中也并不称作变量。 - Chris Smith
1个回答

13

在 Haskell 中,你只需要在外部作用域创建可变变量(实际上是引用),并在内部作用域中使用它。

这里我使用了 ST s monad 来说明原理,但你也可以使用 IO 和许多其他种类的引用来完成相同的操作。

import Control.Monad.ST
import Data.STRef

three :: ST s Int
three = do
   n <- newSTRef 0
   let inc = modifySTRef' n (+1)
   inc
   inc
   inc
   readSTRef n

main :: IO ()
main = print (runST three)   -- output: 3

注意,在一般情况下,inc 可能是一个大的 do 块,在其范围内创建新的引用(新的局部变量),并且具有自己的内部块。与Python一样,作用域的嵌套没有限制。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接