为什么在函数式编程中使用持久性数据结构?


Answers:


19

当您使用不可变数据对象时,函数具有的属性是,每次您使用相同的输入调用它们时,它们都会产生相同的输出。这使概念化计算和正确处理变得更加容易。这也使它们更易于测试。

那只是一个开始。由于数学与函数长期合作,因此我们可以从数学中借鉴许多推理技术,并将其用于程序的严格推理。从我的角度来看,最重要的优点是功能程序的类型系统已得到完善。因此,如果您在某个地方犯了错误,则很有可能将其显示为类型不匹配。因此,类型化的功能程序往往比命令性程序更可靠。

相反,当使用可变数据对象时,首先要承担记住和管理对象在计算过程中经历的多个状态的认知负担。您必须注意以正确的顺序执行操作,并确保此时满足特定步骤所需的所有属性。容易犯错误,并且类型系统的功能不足以捕获这些错误。

数学从未与可变数据对象一起使用。因此,没有可以从中借鉴的推理技术。我们在计算机科学中开发了很多自己的技术,尤其是Floyd-Hoare Logic。但是,与标准数学技巧相比,掌握这些技巧更具挑战性,大多数学生无法使用它们,因此很少接受教学。

要快速了解这两种范例的不同之处,您可以参考我的讲义的前几讲 编程语言原理的


这对我来说很有意义。感谢您分享您的PPT。您是否也共享相同的视频记录?
gpuguy

@gpuguy。我不太会使用Powerpoint。白板是我最喜欢的媒介。但是讲义本身应该很容易阅读。
Uday Reddy

+1数学从未与可变数据对象一起使用。也是您的讲义的链接。
Guy Coder

15

正确使用持久性数据结构比使用可变数据结构要容易。我想说,这是主要优势。

当然,从理论上讲,我们对持久性数据结构所做的任何事情我们也可以对可变数据结构做任何事情,反之亦然。在许多情况下,相关的数据结构会带来额外的成本,通常是因为必须复制其中的一部分。这些考虑因素会使30年前持久性数据结构的吸引力大大降低,那时超级计算机的内存少于您的手机。但是如今,软件生产的主要瓶颈似乎是开发时间和维护成本。因此,我们愿意为了提高开发效率而牺牲执行效率。

为什么使用持久数据结构更容易?因为人类真的不擅长跟踪别名和程序不同部分之间的其他意外交互。他们不由自主地想到这是因为两件事情被称为xy,所以它们之间没有任何共同之处。毕竟,要弄清楚“晨星”和“晚星”确实是同一回事。同样,很容易忘记某个数据结构可能会更改,因为其他线程正在使用它,或者因为我们调用了一种碰巧会更改数据结构的方法,等等。持久数据结构。

持久数据结构还具有其他技术优势。优化它们通常比较容易。例如,如果愿意,您可以随时将持久性数据结构复制到云中的其他节点上,而不必担心同步。


当它具有如此多的优势时,为什么不同时在命令性语言中使用持久性数据结构呢?
gpuguy

4
也许很快您会问“为什么使用命令式语言?”
Andrej Bauer

4
但严重的是,有些数据结构很难用持久性数据结构代替,例如,使用数组和矩阵的数字运算程序在传统数据结构中要快得多,因为硬件已针对此类事物进行了优化。
Andrej Bauer

1
@gpuguy。只要适用且合适,持久性数据结构也可以并且应该以命令式语言使用。为了能够使用它们,该语言应支持基于垃圾回收的内存管理。许多现代语言有:Java和C#,斯卡拉,Python和Ruby,Java脚本等
乌代·雷迪

可以说,一大优势是与可变数据结构相比,接口更加抽象。您可以在后台进行更改(参见不变性与参照完整性),但不必这样做。
拉斐尔

2

除了增加他人的答案并加强数学方法外,函数式编程还可以与关系代数和Galois Connections很好地协同工作。

这在形式方法领域非常有用。
例如:

  • 通过扩展静态检查简化了程序验证中的形式证明;
  • 关系代数的许多特性都可以通过诸如Alloy之类的工具用于SAT求解。
  • 伽罗瓦连接允许的测算方法进行软件规范,在看到这个博客,与参考的,信成穆和何塞努诺·奥利维拉。
  • Galois Connections(和函数式编程)可以按合同设计方式使用,因为它们是比Hoare Logic更笼统的概念。

{p}P{q}[P]ϕpϕq[P]

  • [P]P
  • ϕpϕqpq

这种方法还允许最弱的前提条件最强的前提条件计算,这在许多情况下都非常有用。


2

我想从低层次理解如果数据结构不是持久性的,将会发生什么?

让我们看一下具有巨大状态空间的伪随机数生成器(例如状态为2450字节的“ 梅森扭曲 ”)作为数据结构。我们确实不希望多次使用任何随机数,因此似乎没有理由将其实现为不变的持久数据结构。现在,让我们自问一下以下代码中可能出什么问题:

mt_gen = CreateMersenneTwisterPRNGen(seed)
integral = MonteCarloIntegral_Bulk(mt_gen) + MonteCarloIntegral_Boundary(mt_gen)

大多数编程语言不指定在该命令MonteCarloIntegral_BulkMonteCarloIntegral_Boundary进行评估。如果两者都将对可变mt_gen的引用作为参数,则此计算的结果可能取决于平台。更糟糕的是,在某些平台上,不同运行之间的结果根本无法重现。

可以为mt_gen设计一种有效的可变数据结构,以使的执行交织MonteCarloIntegral_BulkMonteCarloIntegral_Boundary给出“正确”的结果,但是不同的交织通常会导致不同的“正确”的结果。这种不可复制性使相应的功能“不纯净”,并导致其他一些问题。

通过强制执行固定的顺序执行顺序可以避免不可重复性。但是在那种情况下,代码可以这样安排:在任何给定时间仅对mt_gen的单个引用可用。在类型化的函数编程语言中,可以使用唯一性类型来强制执行此约束,从而在纯函数式编程语言的上下文中也可以进行安全的可变更新。所有这些听起来都不错,但至少在理论上,蒙特卡洛模拟令人尴尬地平行,而我们的“解决方案”只是销毁了此属性。这不仅是一个理论问题,而且是一个非常实际的实际问题。但是,我们必须修改伪随机数生成器(由其提供的功能)及其产生的随机数序列,并且没有编程语言可以自动为我们执行此操作。(当然,我们可以使用已经提供所需功能的其他伪随机数库。)

在低级别上,如果执行顺序不是顺序和固定的,则可变数据结构很容易导致不可重复性(并因此导致杂质)。解决这些问题的典型命令性策略是具有固定执行顺序的顺序阶段,其中可变数据结构被更改;具有并行执行顺序的并行阶段,所有共享的可变数据结构都保持恒定。


Andrej Bauer提出了可变数据结构的别名问题。有趣的是,不同的命令式语言(例如Fortran和C)对于允许的函数参数别名具有不同的假设,并且大多数程序员都不知道他们的语言完全具有别名模型。

不变性和价值语义可能会被高估。更重要的是,您的编程语言的类型系统和逻辑框架(如抽象机模型,别名模型,并发模型或内存管理模型)为使用“高效”数据“安全”工作提供了充分的支持。结构。从理论的角度来看,在C ++ 11中引入“移动语义”可能看起来像是在纯度和“安全性”方面迈出了一大步,但实际上却相反。语言的类型系统和逻辑框架已得到扩展,以消除与新语义相关的大部分危险。(即使仍然存在粗糙的边缘,这并不意味着“更好”无法改善


Uday Reddy提出了一个问题,即数学从未使用过可变数据对象,并且针对不可变数据对象开发了功能程序的类型系统。这让我想起了让·伊夫·吉拉德(Jean-Yves Girard)的解释,即当他试图激发线性逻辑时,数学并不用于可变对象。

人们可能会问如何扩展功能性编程语言的类型系统和逻辑框架,以允许“安全”地使用“有效”可变的非持久性数据结构。这里的一个问题可能是经典逻辑和布尔代数可能不是处理可变数据结构的最佳逻辑框架。也许线性逻辑和可交换的等式更适合于该任务?也许我应该读菲利普·沃德勒(Philip Wadler)关于线性逻辑作为函数式编程语言的类型系统的内容?但是,即使线性逻辑不能解决该问题,也不意味着不能将功能编程语言的类型系统和逻辑框架扩展为允许“安全”和“高效”。


@DW您可能是正确的,这个答案不是一个独立的答案。目前仅在Uday Reddy和Andrej Bauer的答案中提出的某些观点上进行扩展。我认为我可以将其修改为独立的,并直接回答“我想从低层次理解如果数据结构不是持久性会发生什么?” 问题的一部分。我将看一下具有巨大状态空间的伪随机数生成器(例如具有2450字节状态的“梅森扭转器”)作为数据结构,并说明可能出错的地方。
Thomas Klimpel 2014年

@DW我觉得这个问题的答案都不能真正回答这个问题。特别是,持久性数据结构实际上是什么(除了不可改变的)以及如何实现它们并没有多大关系。
Guildenstern 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.