所有功能语言都使用垃圾回收吗?


32

是否有一种允许使用堆栈语义的功能语言-作用域末尾的自动确定性销毁?


确定性破坏实际上仅对副作用有帮助。在函数式编程的上下文中,这仅意味着确保某些(单峰)动作始终在序列的末尾运行。顺便说一句,编写不需要垃圾收集的功能连接语言很容易。
乔恩·普迪

我对这个问题感兴趣,这与这件事有什么关系?
mattnz'3

1
在没有垃圾收集的功能语言中,我看不到如何实现不可变数据结构的结构共享。可能可以创建这样一种语言,但是我不会使用这种语言。
dan_waterworth 2012年

Rust具有许多通常被称为“功能性”的功能(至少,非功能性lang中通常希望将它们作为功能性功能)。我很好奇它丢失了什么。默认情况下不可变,闭包,函数传递,有原则的重载,ADT(尚无GADT),模式匹配,所有这些都没有GC。还有什么?
Noein

Answers:


10

虽然我不是函数式编程专家,但我不知道。

原则上似乎很困难,因为从函数返回的值可能包含对在同一函数内(在堆栈上)创建的其他值的引用,或者可能像参数一样容易地被传入,或被传入的东西引用作为参数。在C语言中,此问题的解决方法是:如果程序员未正确处理,则可能会发生悬空指针(或更准确地说,未定义的行为)。那不是功能语言设计师认可的那种解决方案。

但是,有潜在的解决方案。一种想法是使值的生存期与值的引用一起成为值的类型的一部分,并定义基于类型的规则,以防止堆栈分配的值从a的返回或引用。功能。我还没有解决所有的隐患,但是我怀疑那将是可怕的。

对于单子代码,还有(实际上或几乎)单子的另一种解决方案,它可以提供一种自动确定性破坏的IORef。原理是定义“嵌套”动作。组合在一起时(使用关联运算符),它们定义了一个嵌套控制流-我认为是“ XML元素”,其中最左边的值提供了外部的开始和结束标记对。这些“ XML标记”只是在另一抽象级别上定义单子动作的顺序。

在某个点上(在关联组合链的右侧),您需要某种终止符来结束嵌套-需要一些终止符来填充中间的孔。需要终止符可能意味着嵌套合成运算符不是一元运算符,尽管同样,我不确定我是否还没有研究细节。正如所有应用终止符的操作一样,它实际上是将嵌套动作有效地转换为组合的正常单子动作-不一定会影响嵌套合成运算符。

这些特殊操作中有许多将具有无效的“结束标记”步骤,并将“开始标记”步骤等同于一些简单的单调操作。但是有些将代表变量声明。它们将用begin-tag表示构造函数,并用end-tag表示析构函数。所以你得到类似...

act = terminate ((def-var "hello" ) >>>= \h ->
                 (def-var " world") >>>= \w ->
                 (use-val ((get h) ++ (get w)))
                )

按照以下执行顺序转换为Monadic合成,每个标记(而非元素)成为正常的Monadic动作...

<def-var val="hello">  --  construction
  <def-var val=" world>  --  construction
    <use-val ...>
      <terminator/>
    </use-val>  --  do nothing
  </def-val>  --  destruction
</def-val>  --  destruction

这样的规则可以允许实现C ++风格的RAII。类似于IORef的引用无法逃脱其范围,原因与普通IORef不能逃脱monad的原因类似-关联组合的规则无法为引用提供逃脱途径。

编辑 -我差点忘了说-我不确定这里有一定范围。重要的是要确保外部变量基本上不能引用内部变量,因此必须有一个限制才能使用这些类似于IORef的引用。同样,我还没有完成所有细节。

因此,构建可以例如打开一个销毁关闭的文件。施工可以打开一个插座,销毁将关闭。基本上,就像在C ++中一样,变量成为资源管理器。但是与C ++不同,没有没有不能自动销毁的堆分配对象。

尽管此结构支持RAII,但仍然需要垃圾回收器。尽管嵌套动作可以分配和释放内存(将其视为资源),但在该内存块和其他内存中仍然存在对(可能共享的)功能值的所有引用。鉴于可以简单地在堆栈上分配内存,从而避免了释放堆的需要,所以真正的意义(如果有的话)是针对其他类型的资源管理。

因此,至少在RAII基于简单嵌套范围的情况下,这样做可以将RAII样式的资源管理与内存管理分开。您仍然需要一个垃圾回收器来进行内存管理,但是您可以安全,及时地自动确定性清除其他资源。


我看不到为什么所有功能语言都需要 GC 。如果您有C ++风格的RAII框架,则编译器也可以使用该机制。共享值对于RAII框架(请参阅C ++ shared_ptr<>)来说没有问题,但您仍然要保持确定性销毁。RAII棘手的一件事是循环引用。如果所有权图是有向无环图,则RAII可以正常工作。
MSalters 2012年

问题是,函数式编程风格实际上是基于闭包/ lambdas /匿名函数构建的。没有GC,您就没有使用闭包的自由,因此您的语言的功能大大降低。
即将来临的风暴2012年

@comingstorm-C ++具有lambda(自C ++ 11起),但没有标准的垃圾收集器。Lambda也在封闭中携带其环境-该环境中的元素可以通过引用传递,也可以通过值传递指针。但是正如我在第二段中所写的那样,C ++允许悬挂指针的可能性-确保程序员永远不会发生是程序员(而不是编译器或运行时环境)的责任。
2012年

@MSalters-确保无法创建任何参考周期会涉及成本。因此,使语言成为该限制的原因至少是不平凡的。分配给指针可能成为非恒定时间的操作。尽管在某些情况下它可能仍然是最佳选择。垃圾回收避免了这一问题,而且成本不同。使程序员负责任是另一回事。没有充分的理由说明为什么当务之急但在函数式语言中不能使悬空指针正常,但是我仍然不建议编写指针悬空Haskell。
2012年

我认为手动内存管理意味着您没有像Lisp或Haskell闭包那样使用C ++ 11闭包的自由。(实际上,我很想了解此折衷的细节,因为我想编写一种功能系统编程语言...)
即将

3

如果您认为C ++是一种功能语言(它具有lambda),那么它就是不使用垃圾收集的语言的示例。


8
如果您不认为C ++是一种功能语言会怎样(恕我直言,它不是,尽管您可以使用C ++编写功能性程序,但也可以使用它编写一些非常不实用的功能性程序。)。
mattnz 2012年

@mattnz然后我猜答案不适用。我不知道在其他语言中会发生什么(例如像哈斯克尔)
BЈовић

9
说C ++是功能性的,就像说Perl是面向对象的……
Dynamic

至少c ++编译器可以检查副作用。(通过const)
tp1 2012年

@ tp1-(1)我希望这不会退缩到谁的语言最好,以及(2)并非如此。首先,真正重要的影响主要是I / O。其次,即使是对可变内存的影响,const也不会阻止它们。即使您假设不可能颠覆类型系统(在C ++中通常是合理的),也存在逻辑常数和“可变” C ++关键字的问题。基本上,尽管有const,您仍然可以有突变。您应该确保结果在逻辑上仍然是相同的,但不一定是相同的表示形式。
2012年

2

我不得不说这个问题定义不明确,因为它假定存在“功能语言”的标准集合。几乎每种编程语言都支持一定数量的功能编程。而且,几乎每种编程语言都支持一定数量的命令式编程。除了文化偏见和流行教条的指导外,有人在哪里划清界限,说哪一种是功能性语言和哪一种命令性语言?

解决这个问题的更好方法是“是否有可能在堆栈分配的内存中支持功能编程”。正如已经提到的,答案是非常困难的。函数式编程风格可随意促进递归数据结构的分配,这需要堆内存(无论是收集垃圾还是对引用计数)。但是,有一种相当复杂的编译器分析技术,称为基于区域的内存分析,利用该技术,编译器可以将堆分成大块,然后可以像堆栈分配那样自动分配和释放。Wikipedia页面列出了该技术的各种实现,适用于“功能”和“命令式”语言。


1
IMO参考计数垃圾收集,拥有堆并不意味着这些是唯一的选择。C mallocs和mfrees使用堆,但是没有(标准)垃圾收集器,并且如果您编写代码来执行此操作,则仅具有引用计数。C ++几乎相同-它具有(按标准,在C ++ 11中)具有内置引用计数的智能指针,但是如果确实需要,您仍然可以手动进行新建和删除。
2012年

声称引用计数不是垃圾收集的一个常见原因是它无法收集引用周期。这当然适用于简单的实现(可能包括C ++智能指针-我没有检查过),但并非总是如此。至少一个Java虚拟机(由IBM,IIRC制造)使用引用计数作为其垃圾收集的基础。
2012年
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.