在非功能设置中实施关闭的问题


18

在编程语言中,闭包是一种流行且经常需要的功能。维基百科说(强调我的):

在计算机科学中,闭包(...)是一个函数,以及对该函数的非局部变量的引用环境。闭包允许函数访问其直接词法范围之外的变量。

因此,闭包本质上是一个(匿名?)函数值,可以使用超出其自身范围的变量。以我的经验,这意味着它可以访问定义点范围内的变量。

实际上,至少在函数式编程之外,这个概念似乎有所不同。不同的语言实现了不同的语义,甚至似乎还有意见之战。许多程序员似乎不知道闭包是什么,只把它们看作是匿名函数。

同样,在实现闭包时似乎存在主要障碍。最值得注意的是,Java 7本应包含它们,但该功能已推回将来的版本。

为什么闭包这么难(难以理解和实现)?这个问题过于笼统和含糊不清,因此让我将重点放在这些相互关联的问题上:

  • 在常见的语义形式主义(小步骤,大步骤等)中表达闭包是否存在问题?
  • 现有的类型系统是否不适合封闭并且无法轻松扩展?
  • 使闭包与传统的基于堆栈的过程转换保持一致是否有问题?

请注意,该问题通常与程序,面向对象和脚本语言有关。据我所知,函数式语言没有任何问题。


好问题。闭包已在Scala中实现,Martin Odersky编写了Java 1.5编译器,因此尚不清楚为什么它们不在Java 7中。C#拥有它们。(我将在以后尝试给出更好的答案。)
戴夫·克拉克

4
不正确的功能语言(例如Lisp和ML)可以很好地容纳闭包,因此,没有内在的语义原因使它们有问题。
吉尔斯(Gilles)'所以别再邪恶了'

我之所以加入该项目,是因为我一直在努力想象闭包的一小步语义可能看起来像什么。很有可能闭包本身并不是问题,但是很难将闭包包含在设计时没有考虑到的语言中。
拉斐尔

1
看看pdfs.semanticscholar.org/73a2/…-Lua的作者提出了非常巧妙的方法,并讨论了实现闭包的一般问题
Bulat

Answers:


10

我可以引导您进入Funarg问题维基百科页面吗?至少这是编译器人员用来引用闭包实现问题的方式。

因此,闭包本质上是一个(匿名?)函数值,可以使用超出其自身范围的变量。以我的经验,这意味着它可以访问定义点范围内的变量。

尽管此定义有意义,但它无助于描述以传统的基于运行时堆栈的语言实现一流功能的问题。当涉及实现问题时,一等函数可以大致分为两类:

  • 函数返回后,永远不会使用函数中的局部变量。
  • 函数返回后可以使用局部变量。

第一种情况(向下的funargs)并不难实现,甚至在较旧的过程语言(例如Algol,C和Pascal)中也可以找到。C类解决了这个问题,因为它不允许嵌套函数,但是Algol和Pascal进行必要的记账以允许内部函数引用外部函数的堆栈变量。

另一方面,第二种情况(向上的funargs)要求将激活记录保存在堆栈外部的堆中。这意味着除非语言运行时包含垃圾回收器,否则很容易泄漏内存资源。虽然今天几乎所有的东西都被垃圾收集了,但是要求一个仍然是一个重要的设计决策,而且在一段时间之前甚至更是如此。


至于Java的特定示例,如果我没记错的话,主要问题实际上不是能够实现闭包,而是如何以与现有功能(例如匿名内部类)无关的方式将闭包引入语言。不会与现有功能发生冲突(例如已检查的异常-这不是一个无法解决的问题,大多数人一开始都不会想到)。

我还可以想到其他使一流函数难以实现的事情,例如,决定如何处理诸如thisselfsuper之类的“魔术”变量,以及如何与现有的控制流操作员进行交互,例如中断返回。(我们是否要允许非本地退货?)。但是最后,一等函数的最近流行似乎表明,不具有它们的语言主要是出于历史原因或由于早期的一些重大设计决策而这样做的。


1
您是否知道有区分上下情况的任何语言?在.NET语言中,希望接收仅向下函数的通用方法可以接收通用类型的结构,以及可以接收诸如byref(在C#中为“ ref参数”)的结构的委托。如果调用者将所有感兴趣的变量封装在结构中,则委托可以是完全静态的,从而无需进行堆分配。编译器没有为此类构造提供任何不错的语法帮助,但是框架可以支持它们。
2014年

2
@supercat:Rust有多种闭包类型,如果内部函数需要使用堆,则可以在编译时强制执行。但是,这并不意味着实现不能在不强迫您关心所有这些额外类型的情况下避免堆分配。编译器可以尝试推断函数的生命周期,或者可以使用运行时检查,以便仅在严格需要时才将变量延迟保存到堆中(有关详细信息,请参见Lua Evolution论文的“词法范围”部分)
hugomg

5

我们可以看看如何在C#中实现闭包。C#编译器执行的转换规模清楚表明,它们实现闭包的方式需要大量工作。可能有更简单的方法来实现闭包,但是我认为C#编译器团队会意识​​到这一点。

考虑以下伪C#(我删去了一些C#特定的东西):

int x = 1;
function f = function() { x++; };
for (int i = 1; i < 10; i++) {
    f();
}
print x; // Should print 9

编译器将其转换为如下所示:

class FunctionStuff {
   int x;
   void theFunction() {
       x++;
   }
}

FunctionStuff theClosureObject = new FunctionStuff();
theClosureObject.x = 1;
for (int i = 1; i < 10; i++) {
    theClosureObject.theFunction();
}
print theClosureObject.x; // Should print 9

(实际上,变量f仍会创建,其中f是'delegate'(=函数指针),但此委托仍与theClosureObject对象关联-为了不熟悉的对象,我省略了此部分使用C#)

这种转换非常庞大且棘手:请考虑闭包内部的闭包以及闭包与C#语言其余功能的相互作用。我可以想象该功能被Java推迟了,因为Java 7已经具有很多新功能。


我可以看到进展情况。具有多个闭包并且主作用域访问同一个变量将很混乱。
拉斐尔

老实说,这更多是由于使用现有的面向对象的框架来实现闭包,而不是因为它们有任何实际问题。其他语言只是将变量分配为单独的,无方法的结构,然后让多个闭包在需要时共享它。
hugomg 2012年

@Raphael:您对闭包内部的闭包有何看法?等等,让我补充
亚历克斯十布林克

5

回答部分问题。Morrisett和Harper描述的形式主义涵盖了包含闭包的高阶多态语言的大小步语义。在这些文章之前,有一些文章提供了您正在寻找的语义。例如,查看SECD机器。在这些语义中添加可变引用或可变局部变量很简单。我看不出提供此类语义存在任何技术问题。


感谢您的参考!它似乎不适合阅读,但是这可能是语义学论文所期望的。
拉斐尔

1
@Raphael:周围可能更简单。我会尽力寻找并联系您。无论如何,图8具有您要寻找的语义。
戴夫·克拉克

也许您可以给出一个大概的概述。您的答案中的中心思想是什么?
拉斐尔

2
@Raphael。也许我可以请您参考我在编程语言课程中使用的讲义,从而为您提供快速入门。请查找讲义8和9
乌代雷迪

1
该链接显示为无效或隐藏在不可见的身份验证之后。(cs.cmu.edu/afs/cs/user/rwh/public/www/home/papers/gcpoly/tr.pdf)。我被禁止了403。
本·弗莱彻
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.