学习Scala的闭包

开发 后端
本文节选自Martin Odersky,Lex Spoon和Bill Venners所著,Regular翻译的《Programming in Scala》的第八章。Scala是一种针对 JVM 将函数和面向对象技术组合在一起的编程语言。

到本章这里,所有函数文本的例子仅参考了传入的参数。例如,(x: Int) => x > 0里,函数体用到的***变量,x > 0,是x,被定义为函数参数。然而也可以参考定义在其它地方的变量:

  1. (x: Int) => x + more // more是多少? 

51CTO编辑推荐:Scala编程语言专题

函数把“more”加入参考,但什么是more呢?从这个函数的视点来看,more是个自由变量:free variable,因为函数文本自身没有给出其含义。相对的,x变量是一个绑定变量:bound variable,因为它在函数的上下文中有明确意义:被定义为函数的***参数,一个Int。如果你尝试独立使用这个函数文本,范围内没有任何more的定义,编译器会报错说:

  1. scala> (x: Int) => x + more  
  2. < console>:5: error: not found: value more  
  3.  (x: Int) => x + more  
  4.  ˆ  
另一方面,只要有一个叫做more的什么东西同样的函数文本将工作正常:

  1. scala> var more = 1 
  2. more: Int = 1 
  3. scala> val addMore = (x: Int) => x + more  
  4. addMore: (Int) => Int = < function>  
  5. scala> addMore(10)  
  6. res19: Int = 11 
依照这个函数文本在运行时创建的函数值(对象)被称为闭包:closure。名称源自于通过“捕获”自由变量的绑定对函数文本执行的“关闭”行动。不带自由变量的函数文本,如(x: Int) => x + 1,被称为封闭术语:closed term,这里术语:term指的是一小部分源代码。因此依照这个函数文本在运行时创建的函数值严格意义上来讲就不是闭包,因为(x: Int) => x + 1在编写的时候就已经封闭了。但任何带有自由变量的函数文本,如(x: Int) => x + more,都是开放术语:open term。因此,任何依照(x: Int) => x + more在运行期创建的函数值将必须捕获它的自由变量,more,的绑定。由于函数值是关闭这个开放术语(x: Int) => x + more的行动的最终产物,得到的函数值将包含一个指向捕获的more变量的参考,因此被称为闭包。

这个例子带来一个问题:如果more在闭包创建之后被改变了会发生什么事?Scala里,答案是闭包看到了这个变化。如下:

  1. scala> more = 9999 
  2. more: Int = 9999 
  3. scala> addMore(10)  
  4. res21: Int = 10009 
直觉上,Scala的闭包捕获了变量本身,而不是变量指向的值。相对的,Java的内部类根本不允许你访问外围范围内可以改变的变量,因此到底是捕获了变量还是捕获了它当前具有的值就没有差别了。就像前面演示的例子,依照(x: Int) => x + more创建的闭包看到了闭包之外做出的对more的变化。反过来也同样。闭包对捕获变量作出的改变在闭包之外也可见。下面是一个例子:

  1. scala> val someNumbers = List(-11, -10, -50510)  
  2. someNumbers: List[Int] = List(-11, -10, -50510)  
  3. scala> var sum = 0 
  4. sum: Int = 0 
  5. scala> someNumbers.foreach(sum += _)  
  6. scala> sum  
  7. res23: Int = -11 
例子用了一个循环的方式计算List的累加和。变量sum处于函数文本sum += _的外围,函数文本把数累加到sum上。尽管这是一个在运行期改变sum的闭包,作为结果的累加值,-11,仍然在闭包之外可见。

如果闭包访问了某些在程序运行时有若干不同备份的变量会怎样?例如,如果闭包使用了某个函数的本地变量,并且函数被调用很多次会怎样?每一次访问使用的是变量的哪个实例?

仅有一个答案与语言余下的部分共存:使用的实例是那个在闭包被创建的时候活跃的。例如,以下是创建和返回“递增”闭包的函数:

  1. def makeIncreaser(more: Int) = (x: Int) => x + more  
每次函数被调用时都会创建一个新闭包。每个闭包都会访问闭包创建时活跃的more变量。

  1. scala> val inc1 = makeIncreaser(1)  
  2. inc1: (Int) => Int = < function>  
  3. scala> val inc9999 = makeIncreaser(9999)  
  4. inc9999: (Int) => Int = < function>  
调用makeIncreaser(1)时,捕获值1当作more的绑定的闭包被创建并返回。相似地,调用makeIncreaser(9999),捕获值9999当作more的闭包被返回。当你把这些闭包应用到参数上(本例中,只有一个参数,x,必须被传入),回来的结果依赖于闭包被创建时more是如何定义的:

  1. scala> inc1(10)  
  2. res24: Int = 11 
  3. scala> inc9999(10)  
  4. res25: Int = 10009 

尽管本例中more是一个已经返回的方法调用的参数也没有区别。Scala编译器在这种情况下重新安排了它以使得捕获的参数继续存在于堆中,而不是堆栈中,因此可以保留在创建它的方法调用之外。这种重新安排的工作都是自动关照的,因此你不需要操心。请任意捕获你想要的变量:val,var,或参数。

【相关阅读】

  1. Scala的偏应用函数
  2. Scala:函数文本的短格式和占位符语法
  3. 介绍Scala的***类函数
  4. Scala的本地函数:将私有方法转换为本地方法
  5. Scala中定义函数的方法:method

责任编辑:book05 来源: Artima
相关推荐

2011-05-25 14:48:33

Javascript闭包

2024-01-22 09:51:32

Swift闭包表达式尾随闭包

2011-08-24 17:09:35

LUA闭包函数

2009-07-24 17:30:37

Javascript闭

2016-09-14 09:20:05

JavaScript闭包Web

2023-11-02 08:53:26

闭包Python

2021-02-21 16:21:19

JavaScript闭包前端

2009-11-16 17:04:46

Inside Scal

2009-07-21 16:58:31

Scala变量范围

2016-11-01 09:18:33

Python闭包

2010-06-23 10:24:42

Javascript闭

2016-09-18 20:53:16

JavaScript闭包前端

2019-11-07 21:51:18

闭包前端函数

2013-05-02 09:44:57

PHP闭包

2016-10-27 19:26:47

Javascript闭包

2012-11-29 10:09:23

Javascript闭包

2020-09-18 14:12:28

闭包Rsut函数

2021-06-29 09:01:50

Swift闭包语言

2020-10-14 15:15:28

JavaScript(

2010-01-19 09:50:54

Java 7闭包
点赞
收藏

51CTO技术栈公众号