【问题标题】:A Functional-Imperative Hybrid功能命令式混合
【发布时间】:2011-10-27 19:08:11
【问题描述】:

纯函数式编程语言不允许可变数据,但某些计算以命令式方式更自然/直观地表达——或者算法的命令式版本可能更有效。我知道大多数函数式语言都不是纯粹的,它们允许您分配/重新分配变量并执行命令式的事情,但通常不鼓励这样做。

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

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

【问题讨论】:

  • 考虑查看现有语言,包括 SML 和 Clojure。
  • 你所说的“本地状态”让我想起了 Haskell 的 ST monad。另外值得注意的是清洁或线性类型的 ATS 的唯一性类型,它们还允许您以对程序强加刚性结构为代价来获得内存和其他资源的可变性和安全回收。 Clean 的唯一性类型保留了引用透明度。
  • +1 好问题。正如您所说,大多数函数式语言都是不纯的。我反对你关于不鼓励杂质的断言(例如,哈希表在 OCaml 和 F# 中都很常见),但 API 往往是纯粹的函数式。将突变限制为本地人的问题在于它与您想要的完全相反。可变局部变量实际上用途有限,但可变堆分配集合非常有用,主要是因为它们比纯粹的功能等价物快得多。这就是 HLVM 禁止可变局部变量但允许可变数组的原因。

标签: functional-programming state purely-functional imperative


【解决方案1】:

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

好问题。我认为答案是可变局部变量的实用价值有限,但可变堆分配的数据结构(主要是数组)非常有价值,并且构成了许多重要集合的支柱,包括高效的堆栈、队列、集合和字典。因此,仅将突变限制为本地人不会给纯函数式语言带来任何突变的重要好处。

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

在这种情况下,排序是一个很好的案例研究。 Sedgewick 的 C 语言快速排序简短而简单,比任何语言中最快的纯函数排序快数百倍。原因是快速排序会就地改变数组。可变的本地人不会有帮助。大多数图算法也是如此。

【讨论】:

    【解决方案2】:

    简短的回答是:有一些系统可以满足您的需求。例如,您可以使用 Haskell 中的 ST monad(在 cmets 中引用)来完成。

    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)实现的纯结构产生良好效果。人们可以在有限的时间内摆脱不变性来做一些改变底层数组的事情。例如,可以将不可变的 Vectorimpure algorithms package 结合起来,以保持原地排序算法的大部分性能特征,并且仍然保持纯度。

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-03-16
      • 2014-08-25
      • 1970-01-01
      • 2021-12-22
      • 1970-01-01
      • 2018-04-30
      • 1970-01-01
      相关资源
      最近更新 更多