25

我发现 Scala 总是对任何事情都有“自然的解释”。总是像“哦,但这只是在这个和那个对象上使用这个和那个参数调用的函数”。从某种意义上说,没有什么是真正的编译器魔法,正如我们从其他语言中所知道的那样。

我的问题是关于以下代码中使用的<-运算符:

for(i <- 0 to 10) println(i)

在这个例子中,我可以看到它被重写为:

0.to(10).foreach((i:Int)=>println(i))

但这并不能解释是如何进入 foreach 函数内的匿名函数的。在您编写i时,它不是一个对象,也不是一个声明的变量。那么它是什么,它是如何被转移到 foreach 内部的呢?

我的猜测是我终于发现了一些实际上是编译器魔法的东西

谢谢你的时间。

为了澄清,我的问题是: <- 运算符如何在第一行代码中工作,因为 i 不是可以作为函数调用的对象。

4

3 回答 3

63

为了补充 Dave 的答案,这里是 Scala 语言规范中“理解”的翻译模式:

推导计算由枚举器枚举生成的每个绑定的for (enums) yield e表达式。e枚举器序列总是以生成器开始;这之后可以是进一步的生成器、值定义或守卫。

生成器从以某种方式与 pattern 匹配p <- e的表达式生成绑定。值定义将值名称(或模式中的多个名称)绑定到计算表达式的结果。守卫包含一个限制枚举绑定的布尔表达式。epval p = eppeif e

生成器和守卫的确切含义是通过转换为四种方法的调用来定义的:mapfilterflatMapforeach. 这些方法可以针对不同的运营商类型以不同的方式实施。

翻译方案如下。在第一步中,每个生成器p <- e,其中 p 的类型不可辩驳(第 8.1 节)e被替换为

 p <- e.filter { case p => true; case _ => false }

然后,重复应用以下规则,直到消除所有理解。

  • 一个 for-comprehensionfor (p <- e) yield e0被翻译成e.map { case p => e0 }.

  • 一个 for-comprehensionfor (p <- e) e0被翻译成e.foreach { case p => e0 }.

  • 一个为了理解for (p <- e; p0 <- e0 . . .) yield e00,在哪里。. . 是一个(可能为空的)生成器或守卫序列,被翻译为:
    e.flatMap { case p => for (p0 <- e0 . . .) yield e00 }.

  • 一个用于理解的for (p <- e; p0 <- e0 . . .) e00地方。. . 是一个(可能为空的)生成器或守卫序列,被翻译为:
    e.foreach { case p => for (p0 <- e0 . . .) e00 }.

  • p <- e后面跟着守卫的生成器if g被转换为单个生成器:
    p <- e.filter((x1, . . . , xn) => g )
    where x1, . . . ,xn是 的自由变量p

  • p <- e后跟值定义的生成器val p0 = e0被转换为以下值对生成器,其中xx0是新名称:

    val (p, p0) <- 
      for(x@p <- e) yield { val x0@p0 = e0; (x, x0) }
    
于 2010-09-20T18:59:33.313 回答
18

<-是语言定义的关键字符号,与(定义的符号)一样,=>但与之形成鲜明对比->。因为它是基本 Scala 语法的一部分,所以它可用于创建绑定(i在您的示例中),这是用户定义的构造无法完成的。

于 2010-09-20T19:37:35.343 回答
7

在这种情况下,它确实有点编译器的魔力。从 for-comprehension 到 filter/map/flatmap 形式的转换是一种特殊的脱糖,很像 update 和 apply 方法的特殊形式的转换。

于 2010-09-20T18:23:31.357 回答