纯函数式编程语言不允许可变数据,但某些计算以命令式方式更自然/直观地表达 - 或者算法的命令式版本可能更有效。我知道大多数函数式语言都不是纯粹的,让你分配/重新分配变量并执行命令性的事情,但通常不鼓励它。
我的问题是,为什么不允许局部状态在局部变量中被操纵,但是要求函数只能访问它们自己的局部和全局常量(或者只是在外部范围中定义的常量)?这样,所有函数都保持引用透明性(它们在给定相同参数的情况下总是给出相同的返回值),但在函数内,计算可以用命令式术语表示(例如,while循环)。
IO等仍然可以通过正常的功能方式完成 - 通过monad或绕过“world”或“universe”令牌。
答案 0 :(得分:3)
我的问题是,为什么不允许局部状态在局部变量中被操作,但是要求函数只能访问它们自己的局部和全局常量(或者只是在外部范围中定义的常量)?
好问题。我认为答案是可变本地具有有限的实用价值,但可变的堆分配数据结构(主要是数组)非常有价值,并构成许多重要集合的主干,包括高效的堆栈,队列,集合和字典。因此,限制突变到本地只不会给出一种纯粹功能性的语言突变的任何重要好处。
在相关的说明中,交换顺序过程交换纯功能数据结构提供了两个世界的许多好处,因为顺序过程可以在内部使用变异,例如,可变消息队列比任何纯功能队列快〜10倍。例如,这在F#中是惯用的,其中MailboxProcessor
中的代码使用可变数据结构,但它们之间传递的消息是不可变的。
在此背景下,排序是一个很好的案例研究。 Sedgewick在C语言中的快速排序简短,比任何语言中最快的纯功能排序快数百倍。原因是quicksort就地改变了数组。可变的当地人无济于事。大多数图算法也是如此。
答案 1 :(得分:3)
简短的回答是:有些系统可以满足您的需求。例如,您可以使用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 package)实现的纯结构产生良好效果。人们可以在有限的时间内抛弃不变性来做一些改变底层阵列的事情。例如,可以将不可变Vector
与impure algorithms package结合起来,以保持现场排序算法的大部分性能特征,并且仍然可以获得纯度。
在这种情况下,它看起来像:
pureSort :: Ord a => Vector a -> Vector a
pureSort vector = runST $ do
mutableVector <- thaw vector
sort mutableVector
freeze mutableVector
thaw
和freeze
函数是线性时间复制,但这不会破坏整个O(n lg n)运行时间。您甚至可以使用unsafeFreeze
来避免另一次线性遍历,因为不会再次使用可变向量。