Scala 闭包 匿名函数

  • Post author:
  • Post category:其他



函数语言的名称源于这样一种概念:程序行为应该像数学函数一样;换句话说,给定一组输入,函数应始终返回相同的输出。这不仅意味着每个函数必须返回一个值,还意味着从一个调用到下一个调用,函数本质上不得具有内蕴状态(intrinsic state)。这种无状态的内蕴概念(在函数/对象领域中,默认情况下指的是永远不变的对象),是函数语言被认为是并发领域伟大的 “救世主” 的主要原因。


闭包函数作为一级概念的一个含义是,它们必须被识别为单独的结构,也称为闭包,这是 Java 社区最近一直热烈争论的话题。在 Scala 中,这很容易完成。考虑清单 3 中的程序,此程序定义了一个函数,该函数每隔一秒调用一次另一个函数:




清单 3. Timer1.scala



object Timer


{


def oncePerSecond(): unit =


{


while (true)


{


System.out.println(“Time flies when you’re having fun(ctionally)…”)


Thread.sleep(1000)


}


}




def main(args: Array[String]): unit =


{


oncePerSecond


}


}






不幸的是,这个特殊的代码并没有什么功能 …… 或者甚至没任何用处。例如,如果想要更改显示的消息,则必须修改

oncePerSecond

方法的主体。传统的 Java 程序员将通过为

oncePerSecond

定义

String

参数来包含要显示的消息。但甚至这样也是极端受限的:其他任何周期任务(比如 ping 远程服务器)将需要各自版本的

oncePerSecond

,这很明显违反了 “不要重复自己” 的规则。我认为我可以做得更好。


清单 4. Timer2.scala



object Timer


{


def oncePerSecond(callback: () => unit): unit =


{


while (true)


{


callback()


Thread.sleep(1000)


}


}




def timeFlies(): unit =


{ Console.println(“Time flies when you’re having fun(ctionally)…”); }




def main(args: Array[String]): unit =


{


oncePerSecond(timeFlies)


}


}







现在,事情开始变得有趣了。在清单 4 中,函数


oncePerSecond


接受一个参数,但其类型很陌生。

形式上,名为

callback

的参数接受一个函数作为参数。只要传入的函数不接受任何参数(以 () 指示)且无返回(由 => 指示)值(由函数值 unit 指示),就可以使用此函数。然后请注意,在循环体中,我使用

callback

来调用传递的参数函数对象。


幸运的是,我在程序的其他地方已经有了这样一个函数,名为

timeFlies

。所以,我从

main

中将其传递给

oncePerSecond

函数。(您还会注意到,

timeFlies

使用了一个 Scala 引入的类

Console

,它的用途与

System.out

或新的

java.io.Console

类相同。这纯粹是一个审美问题;

System.out



Con


匿名函数,您的函数是什么?


现在,这个

timeFlies

函数似乎有点浪费 — 毕竟,它除了传递给

oncePerSecond

函数外毫无用处。所以,我根本不会正式定义它,如清单 5 所示:


清单 5. Timer3.scala



object Timer


{


def oncePerSecond(

callback: () => unit

): unit =


{


while (true)


{


callback()


Thread.sleep(1000)


}


}




def main(args: Array[String]): unit =


{




oncePerSecond(() =>


Console.println(“Time flies… oh, you get the idea.”))


}


}





在清单 5 中,

主函数将一块任意代码作为参数传递给

oncePerSecond

,看起来像来自 Lisp 或 Scheme 的 lambda 表达式,事实上,这是另一种闭包。这个匿名函数 再次展示了将函数当作一级公民处理的强大功能,它允许您在继承性以外对代码进行全新地泛化。(Strategy 模式的粉丝们可能已经开始唾沫横飞了。)


事实上,

oncePerSecond

仍然太特殊了:它具有不切实际的限制,即回调将在每秒被调用。我可以通过接受第二个参数指明调用传递的函数的频率,来将其泛化,如清单 6 所示:


清单 6. Timer4.scala



object Timer


{


def periodicCall(seconds: int, callback: () => unit): unit =


{


while (true)


{


callback()


Thread.sleep(seconds * 1000)


}


}




def main(args: Array[String]): unit =


{


periodicCall(1, () =>


Console.println(“Time flies… oh, you get the idea.”))


}


}




这是函数语言中的公共主题:创建一个只做一件事情的高级抽象函数,让它接受一个代码块(匿名函数)作为参数,并从这个高级函数中调用这个代码块。

例如,遍历一个对象集合。无需在 for 循环内部使用传统的 Java 迭代器对象,而是使用一个函数库在集合类上定义一个函数 — 通常叫做 “iter” 或 “map” — 接受一个带单个参数(要迭代的对象)的函数。例如,上述的

Array

类具有一个函数

filter

,此函数在清单 7 中定义:


清单 7. Array.scala 的部分清单



class Array[A]


{


// …


def filter  (p : (A) => Boolean)) : Array[A] = … // not shown


}






清单 7 声明

p

是一个接受由

A

指定的泛型参数的函数,然后返回一个布尔值。Scala 文档表明

filter

“返回一个由满足谓词 p 的数组的所有元素组成的数组”。这意味着如果我想返回我的 Hello World 程序,查找所有以字母 G 开头的命令行参数,则可以编写像清单 8 一样简单的代码:


清单 8. Hello, G-men!



object HelloWorld


{


def main(args: Array[String]): unit = {


args.filter( (arg:String) => arg.startsWith(“G”) )


.foreach( (arg:String) => Console.println(“Found ” + arg) )


}


}







此处,


filter

接受谓词,这是一个隐式返回布尔值(

startsWith()

调用的结果)的匿名函数,并使用

args

中的每个元素来调用谓词。如果谓词返回 true,则它将此值添加到结果数组中。遍历了整个数组之后,它接受结果数组并将其返回,然后此数组立即用作 “foreach” 调用的来源,此调用执行的操作就像它名字的含义一样:

foreach

接受另一个函数,并将此函数应用于数组中的每个元素(在本例中,仅显示每个元素)。


不难想象等同于上述

HelloG.scala

的 Java 是什么样的,而且也不难发现 Scala 版本非常简短,也非常清晰。