【问题标题】:Deforestation in Scala collectionsScala 收藏中的森林砍伐
【发布时间】:2012-01-02 15:51:31
【问题描述】:

从 Scala 集合的设计中,我理解为:

scala> BitSet(1,2,3) map (_ + "a")
res7: scala.collection.immutable.Set[String] = Set(1a, 2a, 3a)

不构建中间数据结构:新 Set 是在使用 Builder 迭代 BitSet 时构建的。事实上,在这种情况下,这很明显,因为一组字符串没有意义。

列表中的地图呢?我很确定以下内容会构建一个中间列表:

scala> List(1,2,3) map (_ -> "foo") toMap
res8: scala.collection.immutable.Map[Int,java.lang.String] =
    Map(1 -> foo, 2 -> foo, 3 -> foo)

即列表List((1,foo), (2,foo), (3,foo))。如果没有,那怎么办?那么下面的呢?

scala> Map.empty ++ (List(1,2,3) map (_ -> "foo"))
res10: scala.collection.immutable.Map[Int,java.lang.String] =
    Map(1 -> foo, 2 -> foo, 3 -> foo)

这一次,从我似乎从++的类型中了解到:

def ++ [B >: (A, B), That]
       (that: TraversableOnce[B])
       (implicit bf: CanBuildFrom[Map[A, B], B, That]): That

认为地图可能是动态构建的,并且没有构建中间列表。

是这样吗?如果是,这是确保森林砍伐的规范方法还是有更直接的语法?

【问题讨论】:

    标签: scala collections implicits


    【解决方案1】:

    您可以使用breakOut 来确保不会创建中间集合。例如:

    // creates intermediate list.
    scala> List((3, 4), (9, 11)).map(_.swap).toMap 
    res542: scala.collection.immutable.Map[Int,Int] = Map(4 -> 3, 11 -> 9)
    
    scala> import collection.breakOut
    import collection.breakOut
    
    // doesn't create an intermediate list.
    scala> List((3, 4), (9, 11)).map(_.swap)(breakOut) : Map[Int, Int]
    res543: Map[Int,Int] = Map(4 -> 3, 11 -> 9)
    

    您可以阅读更多关于它的信息here

    更新:

    如果您阅读breakOut 的定义,您会注意到它基本上是一种创建预期类型的​​CanBuildFrom 对象并将其显式传递给方法的方法。 breakOut 只是让您免于输入以下样板文件。

    // Observe the error message. This will tell you the type of argument expected.
    scala> List((3, 4), (9, 11)).map(_.swap)('dummy)
    <console>:16: error: type mismatch;
     found   : Symbol
     required: scala.collection.generic.CanBuildFrom[List[(Int, Int)],(Int, Int),?]
                  List((3, 4), (9, 11)).map(_.swap)('dummy)
                                                    ^
    
    // Let's try passing the implicit with required type.
    // (implicitly[T] simply picks up an implicit object of type T from scope.)
    scala> List((3, 4), (9, 11)).map(_.swap)(implicitly[CanBuildFrom[List[(Int, Int)], (Int, Int), Map[Int, Int]]])
    // Oops! It seems the implicit with required type doesn't exist.
    <console>:16: error: Cannot construct a collection of type Map[Int,Int] with elements of type (Int, Int) based on a coll
    ection of type List[(Int, Int)].
                  List((3, 4), (9, 11)).map(_.swap)(implicitly[CanBuildFrom[List[(Int, Int)], (Int, Int), Map[Int, Int]]])
    
    // Let's create an object of the required type ...
    scala> object Bob extends CanBuildFrom[List[(Int, Int)], (Int, Int), Map[Int, Int]] {
         |   def apply(from: List[(Int, Int)]) = foo.apply
         |   def apply() = foo.apply
         |   private def foo = implicitly[CanBuildFrom[Nothing, (Int, Int), Map[Int, Int]]]
         | }
    defined module Bob
    
    // ... and pass it explicitly.
    scala> List((3, 4), (9, 11)).map(_.swap)(Bob)
    res12: Map[Int,Int] = Map(4 -> 3, 11 -> 9)
    
    // Or let's just have breakOut do all the hard work for us.
    scala> List((3, 4), (9, 11)).map(_.swap)(breakOut) : Map[Int, Int]
    res13: Map[Int,Int] = Map(4 -> 3, 11 -> 9)
    

    【讨论】:

    • 谢谢,这正是我想要的。我不确定的是为什么 scala 抱怨 List((3, 4), (9, 11)).map(_.swap) : Map[Int,Int] 而不是使用我明确提出的类型约束来选择正确的隐式(就像 read 根据上下文在 haskell 中选择正确的类型类实例一样)。是否只是隐含的方式不起作用(我会完全理解它,我知道子类型化会使一切变得困难)还是我在那种特殊情况下忽略了某些东西?
    • @DuncanMcGregor:自过去 5 天以来,我还没有关闭 REPL。我在日常开发中大量使用它。 :-)
    • @DuncanMcGregor:我的 Windows 终于崩溃了。 :-D
    • @missingfaktor 我的错我确定lotusmuseum.com/files/Mousepad_VIP_Dilbert.JPG
    • @DuncanMcGregor:哈哈。好一个。 :-)
    【解决方案2】:

    示例 1) 正确,没有中间列表

    2) 是的,你得到了一个中间列表。

    3) 再次,是的,您从括号中的内容中获得了一个中间列表。没有“魔法”在发生。如果括号中有内容,则首先对其进行评估。

    我不确定您在这里所说的“砍伐森林”是什么意思:根据维基百科,这意味着消除树结构。如果您的意思是消除中间列表,您应该使用 view。例如,请参见此处:summing a transformation of a list of numbers in scala

    因此,如果没有中间结果,您的示例将是

    BitSet(1,2,3).view.map(_ + "a").toSet
    

    toSet 是必需的,否则您将拥有 IterableView[String,Iterable[_]]

    List(1,2,3).view.map(_ -> "foo").toMap
    
    Map.empty ++ (List(1,2,3).view.map(_ -> "foo"))
    

    还有一个force 方法用于执行转换操作,但这似乎有一个讨厌的习惯,给你一个更通用的类型(也许有人可以评论一个原因):

    scala> Set(1,2,3).view.map(_ + 1).force
    res23: Iterable[Int] = Set(2, 3, 4)
    

    【讨论】:

    • 砍伐森林意味着消除中间树结构。列表只是一个退化树,其中每个节点 :: 都有一个元素叶子作为左子节点,另一个 :: 节点或 Nil 叶子作为右子节点,因此该术语也可以应用于列表.
    • 谢谢。关于 3) 我没想到会发生魔法,但我希望键入上下文会导致 ++ 为工作选择正确的隐式(就像 read 在 haskell 中选择正确的类型类实例一样)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-12-06
    • 2020-11-16
    • 1970-01-01
    • 2021-12-22
    • 2017-03-15
    • 1970-01-01
    • 2021-05-29
    相关资源
    最近更新 更多