势在必行的混合动力

纯粹的函数式编程语言不允许可变数据,但是一些计算更自然/直观地以一种命令式的方式表达 - 或者一种命令式的算法可能更有效。 我意识到大多数函数式语言都不是纯粹的,并且让你分配/重新分配变量并做一些必要的事情,但通常不鼓励它。

我的问题是,为什么不允许本地状态在本地变量中操作,但要求函数只能访问自己的本地和全局常量(或者只是在外部范围中定义的常量)? 这样,所有函数都保持参照透明性(它们总是在给定相同参数的情况下给出相同的返回值),但是在一个函数中,计算可以用命令性术语表示(比如说,一个while循环)。

IO等仍然可以以正常的功能方式完成 - 通过monad或传递“世界”或“宇宙”令牌。


简短的回答是:有系统允许你想要的东西。 例如,你可以在Haskell中使用ST monad来完成它(如注释中所引用的那样)。

ST monad方法来自Haskell的Control.Monad.ST 。 用ST monad编写的代码可以在方便的地方使用引用( STRef )。 最好的部分是,你甚至可以在纯代码中使用ST monad的结果,因为它本质上是独立的(这基本上是你想要的问题)。

这种独立财产的证明是通过类型系统完成的。 ST monad携带状态线程参数,通常用类型变量s 。 当你有这样的计算时,你将得到一元结果,类型如下:

foo :: ST s Int

要把它变成纯粹的结果,你必须使用

runST :: (forall s . ST s a) -> a

您可以阅读这种类型,例如:给我一个s型参数无关紧要的计算,我可以在没有ST行李的情况下将计算结果返回给您。 这基本上保持了可变的ST变量不被转义,因为它们会携带s变量,这会被类型系统捕获。

这可以用于对基本可变结构(如向量包)实现的纯结构的良好效果。 人们可以在有限的时间内抛弃不变性来做一些改变潜在阵列的东西。 例如,可以将不可变的Vector与不纯的算法包结合起来,以保持就地排序算法的大部分性能特征,并且仍然可以获得纯度。

在这种情况下,它看起来像这样:

pureSort :: Ord a => Vector a -> Vector a
pureSort vector = runST $ do
  mutableVector <- thaw vector
  sort mutableVector
  freeze mutableVector

thawfreeze功能是线性时间复制,但这不会破坏整个O(n lg n)运行时间。 您甚至可以使用unsafeFreeze避免另一个线性遍历,因为可变向量不会再次使用。


我的问题是,为什么不允许本地状态在本地变量中操作,但要求函数只能访问自己的本地和全局常量(或者只是在外部范围中定义的常量)?

好问题。 我认为答案是可变本地人的实用价值有限,但可变堆分配数据结构(主要是数组)是非常有价值的,并构成许多重要集合的骨干,包括高效的堆栈,队列,集合和字典。 因此,仅向本地人限制突变不会赋予其他纯功能语言突变的任何重要益处。

在一个相关的说明中,交流顺序进程交换纯功能数据结构提供了两个世界的许多好处,因为顺序进程可以在内部使用突变,例如可变消息队列比任何纯功能队列快10倍。 例如,这在F#中是惯用的,其中MailboxProcessor中的代码使用可变数据结构,但它们之间传递的消息是不可变的。

在这种情况下,排序是一个很好的案例研究。 C中的Sedgewick快速排序既简单又快速,比任何语言中最快的纯功能排序快数百倍。 原因是quicksort就地变异了数组。 可变的当地人无济于事。 大多数图算法都是一样的。

链接地址: http://www.djcxy.com/p/80843.html

上一篇: Imperative Hybrid

下一篇: Does functional programming avoid state?