Scala中的foreach和map初始化器

7

刚看到一种有趣的可能性,可以初始化Scala中的代码块,用于类似foreach或map这样的高阶函数:

(1 to 3) map {
  val t = 5
  i => i * 5
}


(1 to 3) foreach {  
  val line = Console.readLine  
  i => println(line)  
}  

这是一些文档记录的特性,还是我应该避免使用这种结构?我能想象,“初始化”块进入构造函数,闭包本身成为一个apply()方法?

感谢Pat的原始问题 (http://extrabright.com/blog/2010/07/10/scala-question-regarding-readline)

2个回答

12
虽然使用的特征并不罕见,但我承认这是一种相当奇怪的特征组合。基本的技巧在于Scala中的任何块都是一个表达式,类型与块中的最后一个表达式相同。如果最后一个表达式是一个函数,这意味着该块具有函数类型,因此可以用作“map”或“foreach”的参数。在这些情况下会发生什么是,当调用“map”或“foreach”时,块被计算。块评估为一个函数(在第一个示例中为i=> i*5),然后将该函数映射到范围上。
这种结构的一个可能的用途是,该块定义可变变量,结果函数每次调用时都会改变这些变量。变量将被初始化一次,由函数闭包,并且它们的值每次函数调用时更新。
例如,这是一种计算前6个阶乘数的有些令人惊讶的方式。
(1 to 6) map {
      var total = 1
      i => {total *= i;total}
    } 

(顺便说一句,很抱歉使用阶乘作为示例。这要么是那个,要么是斐波那契数列。函数式编程公会的规则。如果你有意见,请去大厅找那些人解决。)

将一个块返回一个函数的不那么命令式的原因是为了在块中先定义辅助函数。例如,如果你的第二个例子改成:

(1 to 3) foreach {  
  def line = Console.readLine  
  i => println(line)  
}
结果是读取了三行并且每行只输出了一次,而你的例子中读取了一行并且输出了三次。

比我的回答更准确。+1 - VonC
在阶乘的例子中,应该使用total *= i,而不是引入一个名为counter的第二个变量。 - Ken Bloom

1

首先,原博客“Scala Question Regarding readLine”的评论提到:

line”是一个值,不能被执行,它只能从“Console.readLine”方法的执行结果中分配一次。
在您的闭包中,它被使用不到三次。
但如果您将其定义为一个方法,则会执行三次:

(1 to 3) foreach {
  def line = Console.readLine
  i => println(line)
}

博客Scala for Java Refugees Part 6: Getting Over Java有一个关于高阶函数的有趣部分,其中包括:

Scala在这些高阶函数的语法上提供了更多的灵活性。
在迭代调用中,我们正在创建一个完整的匿名方法,只是为了调用另一个println(String)方法。
考虑到println(String)本身就是一个接受String并返回Unit的方法,人们可能会认为我们可以将其压缩一下。事实证明,我们可以:

iterate(a, println)

通过省略括号并仅指定方法名称,我们告诉Scala编译器,我们想要将println作为一个函数值使用,并将其传递给iterate方法。
因此,我们不需要创建一个新的方法来处理一组单独的调用,而是传入一个已经实现了我们所需功能的旧方法。
这是在C和C++中经常看到的模式。事实上,将函数作为函数值传递的语法完全相同。似乎有些事情永远不会改变...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接