【问题标题】:scala "yield" doesn't produce the right typescala“产量”不产生正确的类型
【发布时间】:2013-12-29 03:33:18
【问题描述】:

yield 主要用于 for-yield 循环以生成新的同类型集合。例如:

scala> val a = Array(2,3,5)
a: Array[Int] = Array(2, 3, 5)

scala> val result = for (elem <- a) yield 2 * elem
result: Array[Int] = Array(4, 6, 10)

这一切都很好,for循环接受一个数组并返回一个数组。

但后来我注意到了:

scala> 1 to 10
res0: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

这会生成一个范围类型的集合,但是当你将它与 for-yield 循环结合使用时,会发生这种情况:

scala> for (i <- (1 to 10)) yield i + 2
res2: scala.collection.immutable.IndexedSeq[Int] = Vector(3, 4, 5, 6, 7, 8, 9, 10, 11, 12)

进来的类型是范围,但它发出的类型是Vector。为什么会这样?我错过了什么吗?

【问题讨论】:

  • 这是错误的:“yield 主要用于 for-yield 循环以生成新的同类型集合。”
  • 嗯..这是我在yield..@Jean-PhilippePellet 看到的唯一情况,我认为yield 的含义及其用法应该更多
  • @WindDweller 更多。您使用 for-comprehensions 进行单子流控制和组合。看看你是如何使用Future 来理解的,它会让你大吃一惊。还可以查看 Try 处理异常的能力。
  • @WindDweller:第二个问题应该移到一个单独的问题中。虽然你会得到类似的答案,但不会那么混乱。

标签: scala


【解决方案1】:

看看Range:

class Range extends AbstractSeq[Int] with IndexedSeq[Int] with CustomParallelizable[Int, ParRange] with Serializable

然后map的签名:

 def map[B](f: (A) ⇒ B): IndexedSeq[B]

原因是Range 实际上是一个加糖的IndexedSeq,它添加的只是范围特定的行为:

Range.InclusiveRange.Exclusive

map 返回IndexedSeq 的原因可能是编译器限制,因为它无法预测map 操作产生的Range 的类型。

【讨论】:

  • 您能否更具体地了解map 操作?在我看来,我使用的唯一操作是“+”,它不应该是 map 操作。
  • 哈哈这很有趣。我以flatMap 的方式重写了一个 for 循环,我也得到了 Vector 类型。即使在 Scala-doc 中,flatMap 也会返回IndexedSeq,这是VectorRange 共享的一个特征。这可能完全是一个编译器限制,因为似乎在缺少特定序列类型时,默认应用 Vector
  • 如果您的产量是i * 2 而不是i + 2,那么会产生什么Range?此外,要使 i + 2 示例生成 Range,它必须获取所有结果并查看它们是连续值,然后为它们的起始值和结束值构建一个 Range。
  • 实际上,现在我想起来了,Range 不必是连续的。但是它们确实必须在值之间有统一的间隔(例如,1 to 20 by 3),所以仍然有许多yield 表达式的整个结果无法用Range 表示。
【解决方案2】:

不,你没有错过任何东西。查看Rangemap 的签名。

 def map[B](f: (A) ⇒ B): IndexedSeq[B]

这就是它产生您所看到的值的原因。 Range 本身“是”IndexedSeq

为什么我在讨论理解时要谈论map?因为理解是编译器转换的语法糖,它在引擎盖下使用mapflatMapfilter(除其他外)。所以即使你只产生你输入的内容,你也可以调用map identity.

另请注意,Vector 部分是为什么会发生这种情况...

IndexedSeq 是一个特征。如果您要查看此 trait here 的源代码,伴生对象会从 newBuilder[A] 方法生成 Vector

object IndexedSeq extends SeqFactory[IndexedSeq] {
  override lazy val ReusableCBF  = 
      scala.collection.IndexedSeq.ReusableCBF.asInstanceOf[GenericCanBuildFrom[Nothing]]
  class Impl[A](buf: ArrayBuffer[A]) extends AbstractSeq[A] with IndexedSeq[A] with Serializable {
    def length = buf.length
    def apply(idx: Int) = buf.apply(idx)
  }
  def newBuilder[A]: Builder[A, IndexedSeq[A]] = Vector.newBuilder[A]
  implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, IndexedSeq[A]] =
    ReusableCBF.asInstanceOf[GenericCanBuildFrom[A]]
}

【讨论】:

  • 我在想Vector 是否是IndexedSeq 的“首选”类型,这意味着无论何时构造IndexSeq,它都会构造为Vector。那么flatMap 操作是否真的构造了一些新的东西,而忽略了程序员放入Range 类型集合的事实?
  • 如果flatMap适用于理解的结果,那么是的。
  • @WindDweller Range 上的 flatMap 无法返回 Range 结果,因为您提供的函数可以对输入执行任何操作并且不受限制返回等距值,因此结果不能静态保证为Range
  • 你是对的@TimDestan Range 只存储了一个头部、尾部和增量。似乎flatMap 将正确返回ArrayList 或任何其他类型,Range 除外
【解决方案3】:

范围必须在其值之间有一个固定的步长。由于不可能推断出任何收益返回都将是一个范围,因此创建集合以便 map 被定义为返回一个 IndexedSeq,即表现得像一个它覆盖的 IndexedSeq。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-26
    • 1970-01-01
    • 1970-01-01
    • 2020-12-14
    • 2015-10-12
    • 2015-01-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多