【问题标题】:Multiple flatMaps in ScalaScala 中的多个 flatMap
【发布时间】:2012-08-27 08:19:54
【问题描述】:

xs map f map g 相比,编写xs map { x => g(f(x)) } 效率更高,对于多个filter 操作也是如此。

如果我连续有两个或多个flatMaps,有没有办法将它们组合成一个,这可能更有效?例如

def f(x: String) = Set(x, x.reverse)
def g(x: String) = Set(x, x.toUpperCase)

Set("hi", "bye") flatMap f flatMap g  
  // Set(bye, eyb, IH, BYE, EYB, ih, hi, HI)

【问题讨论】:

    标签: scala functional-programming scala-collections


    【解决方案1】:

    为了进行这样的转换,我们需要使用操作所具有的一些标识。例如,正如您所写,map 具有身份

    map f ∘ map g ≡ map (f ∘ g)
    

    (其中 代表函数组合 - 参见Function1.compose(...); 和 代表表达式的等价性)。这是因为带有map 的类可以被视为functors,所以map 的任何合理实现都必须保留这个属性。

    另一方面,具有flatMap 并有办法创建某种单元素实例的类(例如创建单例Set)通常形成monad。所以我们可能会尝试从 monad 规则中推导出一些转换。但是对于flatMap的重复应用,我们可以推断出的唯一身份是

    (set flatMap f) flatMap g ≡ x flatMap { y => f(y) flatMap g }
    

    这是associativity relationship 的一种flatMap 组合。这对优化计算没有多大帮助(实际上它会使情况变得更糟)。所以,结论是,flatMap 没有类似的通用“优化”身份。

    底线是:赋予Set.flatMap 的每个函数都会为其应用的每个元素创建一个新的Set。我们无法避免创建这样的中间集,除非我们完全忘记使用组合 flatMap 并以某种不同的方式解决问题。通常这是不值得的,因为编写flatMaps(或使用for(...) yield ..)更干净、更易读,而且速度上的小小权衡通常不是什么大问题。

    【讨论】:

      【解决方案2】:

      scalaz 中,有一种方法可以将a -> m bb -> m c 等函数组合成a -> m c(就像这里的函数,从StringSet[String])。顺便说一下,它们被称为Kleisli functions。在 haskell 中,只需在这些函数上使用 >=> 即可完成。在 scala 中,您必须更加冗长(顺便说一下,我已经稍微更改了示例:我无法使其与 Set 一起使用,所以我使用了 List):

      scala> import scalaz._, std.list._
      import scalaz._
      import std.list._
      
      scala> def f(x: String) = List(x, x.reverse)
      f: (x: String)List[String]
      
      scala> def g(x: String) = List(x, x.toUpperCase)
      g: (x: String)List[java.lang.String]
      
      scala> val composition = Kleisli(f) >=> Kleisli(g)
      composition: scalaz.Kleisli[List,String,java.lang.String] = scalaz.KleisliFunctions$$anon$18@37911406
      
      scala> List("hi", "bye") flatMap composition
      res17: List[java.lang.String] = List(hi, HI, ih, IH, bye, BYE, eyb, EYB)
      

      【讨论】:

      • +1 仅用于介绍 Kleisli 函数。但这真的更快吗?或者至少合理地预期会更快。我发现很难估计 scalaz 魔术的性能。
      • 是的,我相信,它并没有更快。它为List 使用Bind 实例,具体来说,它是bind 方法,本质上是flatMap。所以,这段代码中隐藏了第二个flatMap
      【解决方案3】:

      您为过滤器描述的方法基本上跳过了中间集合的创建。

      对于flatMap,至少内部集合是在函数内部创建的,所以我无法想象有任何方法可以在不更改函数的情况下跳过该创建。

      您可以尝试使用视图,但我不确定这对 flatMap 是否有用。

      或者,您可以构建一个multiFlatMap,它直接从函数结果构建最终集合,而不将从函数返回的中间集合填充到新集合中。

      不知道这是否可行。我至少看到一些严重的类型挑战即将到来,因为您需要传入一个函数序列,其中每个函数返回一个集合 A,其中 A 是下一个函数的输入类型。至少在任意类型和任意数量的函数的一般情况下,这听起来有些挑战。

      【讨论】:

        猜你喜欢
        • 2020-08-17
        • 1970-01-01
        • 1970-01-01
        • 2020-07-22
        • 2017-04-23
        • 1970-01-01
        • 1970-01-01
        • 2023-03-22
        • 1970-01-01
        相关资源
        最近更新 更多