【问题标题】:Closures in Scala vs Closures in JavaScala 中的闭包与 Java 中的闭包
【发布时间】:2011-09-02 18:42:35
【问题描述】:

前段时间,Oracle 决定在 Java 8 中添加闭包是个好主意。我想知道与从第一天起就关闭的 Scala 相比,那里的设计问题是如何解决的。

引用javac.info未决问题

  1. 方法句柄可以用于函数类型吗? 如何使其工作尚不清楚。一个问题是方法句柄会具体化类型参数,但会干扰函数子类型化。

  2. 我们能否摆脱“抛出”类型参数的显式声明? 这个想法是在声明的绑定是检查异常类型时使用析取类型推断。这不是严格向后兼容的,但不太可能破坏真实的现有代码。然而,由于语法上的歧义,我们可能无法摆脱类型参数中的“抛出”。

  3. 在旧式循环索引变量上禁止 @Shared

  4. 处理定义多个方法的接口(如 Comparator),除了其中一个方法外,所有这些接口都将由继承自 Object 的方法实现。 “单一方法的接口”的定义应仅计算不会由 Object 中的方法实现的方法,并且如果实现其中一个方法将实现所有方法,则应将多个方法计为一个。主要是,这需要更精确地说明接口只有一个抽象方法意味着什么。

  5. 指定从函数类型到接口的映射:名称、参数等。 我们应该完全精确地指定从函数类型到系统生成接口的映射。

  6. 类型推断。 需要扩充类型推断规则以适应异常类型参数的推断。同样,闭包转换使用的子类型关系也应该体现出来。

  7. 省略的异常类型参数有助于改进异常透明度。 也许使省略的异常类型参数意味着界限。这可以通过添加新的泛型异常参数来改造没有异常类型参数的现有泛型接口,例如 java.util.concurrent.Callable。

  8. 函数类型的类字面量是如何形成的? 是 #void().class 吗?如果是这样,如果对象类型被擦除,它是如何工作的?是#?(?).class 吗?

  9. 系统类加载器应动态生成函数类型接口。 与函数类型对应的接口应该由引导类加载器按需生成,以便在所有用户代码之间共享。对于原型,我们可能让 javac 生成这些接口,以便原型生成的代码可以在库存 (JDK5-6) 虚拟机上运行。

  10. 必须每次对 lambda 表达式求值都产生一个新对象吗? 希望不会。例如,如果 lambda 没有从封闭范围捕获变量,则可以静态分配它。类似地,在其他情况下,如果 lambda 未捕获循环内声明的任何变量,则可以将其移出内部循环。因此,如果规范不承诺 lambda 表达式结果的引用标识,那将是最好的,这样编译器就可以完成这样的优化。

据我了解,2.、6. 和 7. 在 Scala 中不是问题,因为 Scala 不像 Java 那样将检查异常用作某种“影子类型系统”。

剩下的呢?

【问题讨论】:

    标签: scala closures language-design java-8


    【解决方案1】:

    我将在这里只解决数字 4。

    Java“闭包”与其他语言中的闭包的区别之一是它们可以用来代替不描述函数的接口——例如,Runnable。这就是SAM,单一抽象方法的意思。

    Java 这样做是因为这些接口在 Java 库中比比皆是,而它们在 Java 库中比比皆是 因为 Java 是在没有函数类型或闭包的情况下创建的。在他们缺席的情况下,每个需要控制反转的代码都必须求助于使用 SAM 接口。

    例如,Arrays.sort 采用 Comparator 对象,该对象将在要排序的数组成员之间进行比较。相比之下,Scala 可以通过接收函数(A, A) => IntList[A] 进行排序,该函数很容易通过闭包传递。但是,请参阅最后的注释 1。

    因此,由于 Scala 的库是为具有函数类型和闭包的语言创建的,因此不需要在 Scala 中支持诸如 SAM 闭包之类的东西。

    当然,还有一个 Scala/Java 互操作性的问题——虽然 Scala 的库可能不需要像 SAM 这样的东西,Java 库却需要。有两种方法可以解决。首先,因为 Scala 支持闭包和函数类型,所以很容易创建辅助方法。例如:

    def runnable(f: () => Unit) = new Runnable {
        def run() = f()
    }
    
    runnable { () => println("Hello") } // creates a Runnable
    

    实际上,通过使用 Scala 的别名参数可以使这个特定的示例更短,但这不是重点。无论如何,可以说,这是 Java 可以做的事情,而不是它打算做的事情。鉴于 SAM 接口的流行,这并不令人惊讶。

    Scala 处理此问题的另一种方式是通过隐式转换。通过在上面的runnable 方法前面加上implicit,可以创建一种方法,只要需要Runnable 但提供了() => Unit 函数,就会自动应用(注2)。

    但是,隐式非常独特,并且在某种程度上仍然存在争议。

    注 1:实际上,这个特定示例是出于恶意选择的...Comparator两个 抽象方法而不是一个,这就是整个问题它。由于其中一种方法可以根据另一种方法实现,我认为他们只会从抽象列表中“减去”防御者方法。

    而且,在 Scala 方面,即使有一个使用 (A, A) => Boolean 而不是 (A, A) => Int 的排序方法,标准排序方法也需要一个 Ordering 对象,这与 Java 的 Comparator 非常相似!不过,在 Scala 中,Ordering 扮演着类型类的角色。

    注意 2:隐式会自动应用,一旦它们被导入作用域

    【讨论】:

      【解决方案2】:

      1) 方法句柄可以用于函数类型吗?

      Scala 针对没有方法句柄的 JDK 5 和 6,因此它还没有尝试处理这个问题。

      2) 我们可以摆脱“抛出”类型参数的显式声明吗?

      Scala 没有检查异常。

      3) 在旧式循环索引变量上禁止 @Shared。

      Scala 没有循环索引变量。不过,同样的想法可以用某种 while 循环来表达。 Scala 的语义在这里非常标准。符号绑定被捕获,如果符号碰巧映射到一个可变的参考单元格,那么你自己就可以了。

      4) 处理像 Comparator 这样定义多个方法的接口,但其中一个方法来自 Object

      Scala 用户倾向于使用函数(或隐式函数)将正确类型的函数强制转换为接口。例如

      [implicit] def toComparator[A](f : (A, A) => Int) = new Comparator[A] { 
          def compare(x : A, y : A) = f(x, y) 
      }
      

      5) 指定从函数类型到接口的映射:

      Scala 的标准库包含 0

      6) 类型推断。需要扩充类型推断的规则以适应异常类型参数的推断。

      由于 Scala 没有检查异常,它可以解决整个问题

      7) 删除​​异常类型参数以帮助改进异常透明度。

      同样的交易,没有检查异常。

      8) 函数类型的类文字是如何形成的?是 #void().class 吗?如果是这样,如果对象类型被擦除,它是如何工作的?是#?(?).class 吗?

      classOf[A => B] //or, equivalently, 
      classOf[Function1[A,B]]
      

      类型擦除是类型擦除。无论 A 和 B 的选择如何,上述文字都会产生 scala.lang.Function1。如果您愿意,可以编写

      classOf[ _ => _ ] // or
      classOf[Function1[ _,_ ]]
      

      9) 系统类加载器应该动态生成函数类型接口。

      Scala 任意限制参数的数量最多为 22 个,这样它就不必动态生成 FunctionN 类。

      10) lambda 表达式的求值必须每次都产生一个新对象吗?

      Scala 规范并没有说必须。但是从 2.8.1 开始,编译器不会优化 lambda 无法从其环境中捕获任何内容的情况。我还没有用 2.9.0 测试过。

      【讨论】:

        猜你喜欢
        • 2013-09-21
        • 2011-03-04
        • 2016-09-28
        • 2016-10-20
        • 2021-03-11
        • 1970-01-01
        • 2011-04-20
        • 2011-07-23
        • 2017-08-28
        相关资源
        最近更新 更多