【问题标题】:Does scala perform optimisations in chained functions?scala 是否在链式函数中执行优化?
【发布时间】:2021-04-19 07:00:46
【问题描述】:
val list1 : List[String] = List(....)
val list2 : List[String] = List(....)
val filtered = list1.filter(list2.toSet.contains)

当使用上面的代码时,toSet 是否会为 list1 中的每个项目调用(在这种情况下我应该添加另一个 val set2 = list2.toSet,或者 Scala 编译器是否对此进行了优化,在这种情况下上面的代码只会运行toSet 一次?

【问题讨论】:

    标签: list scala performance


    【解决方案1】:

    无需优化,toSet 只能运行一次:

    1. eta-expansion rules 用于将 list2.toSet.contains 转换为 lambda。在这种特定情况下,翻译是

      list1.filter({
        val x1 = list2.toSet
        { (y1: String) => x1.contains(y1) }
      })
      
    2. 方法的参数在调用它之前被评估(除了按名称调用的参数,但这不是一个),为了评估这个块val x1 = list2.toSet首先被执行,然后{ (y1: String) => x1.contains(y1) }被返回。

    当然,如果你愿意,你可以更明确。

    【讨论】:

    • @texasbruce 我没有检查,但如果不是,这将是一个错误(如果我正确理解规范)。
    • 这种变化会不会是 filter(list2.toSet.contains) 的函数字面值,例如 filter(x => list2.toSet.contains(x)),或者带有额外的逻辑 x => list2.toSet.contains(x) && x != "baaa"
    • @TheDude 是的,这将在每次调用 lambda 时评估 toSet,因为它现在位于 lambda 体内。这里需要进行优化来避免它。您可以尝试使用-optimize 进行编译(请参阅docs.scala-lang.org/overviews/compiler-options/index.html),然后查看字节码以检查它是否有效,但如果它有效,我会有点惊讶。
    • 在 REPL 中使用//print<TAB>,您可以看到这正是(除了名称)编译器所做的脱糖。
    【解决方案2】:

    为了确保它被调用一次,你可以这样做:

    val list1 : List[String] = List(....)
    val list2 : List[String] = List(....)
    val s: Set[String] = list2.toSet
    val filtered = list1.filter(s.contains)
    

    话虽如此,你可以使用intersect方法:

    list1.intersect(list2)
    

    这个方法位于一个名为StrictOptimizedSeqOps的特征上,它的实现中有注释:// Avoid multiple map lookups所以我猜这是优化的。

    【讨论】:

    • 这不能回答 OP 的问题
    • 我不认为这个检查能达到你想要的效果;如果 Scala 有更简单的 eta 扩展规则并将 list2.toSet.contains 扩展为 (x: String) => list2.toSet.contains(x),它仍然会输出一次 hey,但每个 list1 元素调用一次 list2.toSet
    • @AlexeyRomanov 我明白了。谢谢。我回滚到我之前的答案,该答案没有假设任何关于 eta 扩展的内容。
    猜你喜欢
    • 2012-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-31
    • 2010-10-23
    • 2012-04-18
    • 2011-07-16
    • 1970-01-01
    相关资源
    最近更新 更多