8 state functional-programming imperative purely-functional
纯函数式编程语言不允许可变数据,但某些计算以命令式方式更自然/直观地表达 - 或者算法的命令式版本可能更有效.我知道大多数函数式语言都不是纯粹的,让你分配/重新分配变量并执行命令性的事情,但通常会阻止它.
我的问题是,为什么不允许本地状态在局部变量中被操作,但是要求函数只能访问它们自己的局部和全局常量(或者只是在外部范围中定义的常量)?这样,所有函数都保持引用透明性(它们在给定相同参数的情况下总是给出相同的返回值),但在函数内,计算可以用命令式术语表示(例如,while循环).
IO等仍然可以通过正常的功能方式完成 - 通过monad或绕过"world"或"universe"令牌.
简短的回答是:有一些系统可以满足您的需求。例如,您可以使用STHaskell 中的 monad 来完成此操作(如评论中所引用)。
monadST方法来自 Haskell 的Control.Monad.ST。在 monad 中编写的代码ST可以在方便的地方使用引用 ( STRef)。好的部分是,您甚至可以在纯代码中使用 monad 的结果ST,因为它本质上是独立的(这基本上就是您在问题中想要的)。
这个独立属性的证明是通过类型系统完成的。monadST带有一个状态线程参数,通常用类型变量表示s。当你进行这样的计算时,你将得到一元结果,其类型如下:
foo :: ST s Int
Run Code Online (Sandbox Code Playgroud)
要真正将其转化为纯结果,您必须使用
runST :: (forall s . ST s a) -> a
Run Code Online (Sandbox Code Playgroud)
您可以像这样阅读此类型:给我一个与s类型参数无关的计算,我可以给您返回计算结果,而不需要任何ST负担。这基本上可以防止可变ST变量逃逸,因为它们会携带s,而这将被类型系统捕获。
这对于使用底层可变结构(如向量包)实现的纯结构可以产生良好的效果。人们可以在有限的时间内摆脱不变性,以做一些就地改变底层数组的事情。例如,可以将不可变Vector与不纯的算法包结合起来,以保留就地排序算法的大部分性能特征,并且仍然获得纯度。
在这种情况下,它看起来像:
pureSort :: Ord a => Vector a -> Vector a
pureSort vector = runST $ do
mutableVector <- thaw vector
sort mutableVector
freeze mutableVector
Run Code Online (Sandbox Code Playgroud)
和函数是线性时间复制,但这不会破坏整体 O(n lg n) 运行时间thaw。freeze您甚至可以使用它unsafeFreeze来避免另一次线性遍历,因为可变向量不会再次使用。