在Scala上键入不匹配以便理解

时间:2011-01-18 01:09:53

标签: scala for-loop type-mismatch for-comprehension scala-option

为什么此构造会导致Scala中出现类型不匹配错误?

for (first <- Some(1); second <- List(1,2,3)) yield (first,second)

<console>:6: error: type mismatch;
 found   : List[(Int, Int)]
 required: Option[?]
       for (first <- Some(1); second <- List(1,2,3)) yield (first,second)

如果我用List切换Some,它编译得很好:

for (first <- List(1,2,3); second <- Some(1)) yield (first,second)
res41: List[(Int, Int)] = List((1,1), (2,1), (3,1))

这也可行:

for (first <- Some(1); second <- Some(2)) yield (first,second)

5 个答案:

答案 0 :(得分:112)

为了将理解转换为对mapflatMap方法的调用。例如这一个:

for(x <- List(1) ; y <- List(1,2,3)) yield (x,y)

成为:

List(1).flatMap(x => List(1,2,3).map(y => (x,y)))

因此,第一个循环值(在本例中为List(1))将接收flatMap方法调用。由于flatMap上的List会返回另一个List,因此for comprehension的结果当然是List。 (这对我来说是新的:因为理解并不总是导致流,甚至不一定在Seq中。)

现在,看一下flatMapOption的声明方式:

def flatMap [B] (f: (A) ⇒ Option[B]) : Option[B]

记住这一点。让我们看看理解错误(Some(1)的错误)如何转换为地图调用序列:

Some(1).flatMap(x => List(1,2,3).map(y => (x, y)))

现在,很容易看出flatMap调用的参数是根据需要返回List而不是Option的内容。

为了解决问题,您可以执行以下操作:

for(x <- Some(1).toSeq ; y <- List(1,2,3)) yield (x, y)

编译得很好。值得注意的是,Option不是Seq的子类型,正如通常所假设的那样。

答案 1 :(得分:31)

一个容易记住的提示, for comprehensions 将尝试返回第一个生成器的集合类型,在这种情况下为Option [Int]。因此,如果您从 Some(1)开始,您应该期望Option [T]的结果。

如果您想要 List 类型的结果,则应该从List生成器开始。

为什么有这个限制而不是假设你总是想要某种顺序?您可能会遇到返回Option有意义的情况。也许你有一个Option[Int]想要与某个东西结合以获得Option[List[Int]],比如使用以下函数:(i:Int) => if (i > 0) List.range(0, i) else None;然后,当事情没有“有意义”时,你可以写下这个并获得无:

val f = (i:Int) => if (i > 0) Some(List.range(0, i)) else None
for (i <- Some(5); j <- f(i)) yield j
// returns: Option[List[Int]] = Some(List(0, 1, 2, 3, 4))
for (i <- None; j <- f(i)) yield j
// returns: Option[List[Int]] = None
for (i <- Some(-3); j <- f(i)) yield j
// returns:  Option[List[Int]] = None

如何在一般情况下扩展 for comprehensions 实际上是将M[T]类型的对象与函数(T) => M[U]组合在一起以获得对象的相当通用的机制输入M[U]。在您的示例中,M可以是Option或List。通常,它必须是相同的类型M。因此,您无法将Option与List结合使用。有关M其他内容的示例,请查看subclasses of this trait

为什么当你开始使用List时,List[T](T) => Option[T]的组合有效?在这种情况下,库在有意义的地方使用更通用的类型。因此,您可以将List与Traversable结合使用,并且存在从Option到Traversable的隐式转换。

底线是这样的:考虑你希望表达式返回什么类型,并以该类型作为第一个生成器开始。如有必要,请将其包裹在该类型中。

答案 2 :(得分:4)

它可能与Option不是Iterable有关。隐式Option.option2Iterable将处理编译器期望第二个是Iterable的情况。我希望编译器魔法根据循环变量的类型而不同。

答案 3 :(得分:0)

我总觉得这很有帮助:

scala> val foo: Option[Seq[Int]] = Some(Seq(1, 2, 3, 4, 5))
foo: Option[Seq[Int]] = Some(List(1, 2, 3, 4, 5))

scala> foo.flatten
<console>:13: error: Cannot prove that Seq[Int] <:< Option[B].
   foo.flatten
       ^

scala> val bar: Seq[Seq[Int]] = Seq(Seq(1, 2, 3, 4, 5))
bar: Seq[Seq[Int]] = List(List(1, 2, 3, 4, 5))

scala> bar.flatten
res1: Seq[Int] = List(1, 2, 3, 4, 5)

scala> foo.toSeq.flatten
res2: Seq[Int] = List(1, 2, 3, 4, 5)

答案 4 :(得分:0)

自从 Scala 2.13 选项被制作IterableOnce

sealed abstract class Option[+A] extends IterableOnce[A] with Product with Serializable

因此以下用于理解的内容无需使用 option2Iterable 隐式转换

scala> for {
     |   a <- List(1)
     |   b <- Some(41)
     | } yield (a + b)
val res35: List[Int] = List(42)

scala> List(1).flatMap
final override def flatMap[B](f: Int => scala.collection.IterableOnce[B]): List[B]

我们看到 List#flatMap 将函数带到 IterableOnce。去除上面的糖以供理解,我们得到类似

List(1).flatMap(a => Some(41).map(b => a + b))

这表明没有隐式转换。

然而在 Scala 2.12 和之前的 Option 不是一个可遍历/可迭代的实体

sealed abstract class Option[+A] extends Product with Serializable 

所以上面的理解将 desugar 到类似的东西

List(1).flatMap(a => option2Iterable(Some(41)).map(b => a + b))(List.canBuildFrom[Int])

我们看到隐式转换的地方。

在以 Option 开头,然后我们尝试链接一个 List

scala> for {
     |   a <- Option(1)
     |   b <- List(41)
     | } yield (a + b)
         b <- List(41)
           ^
On line 3: error: type mismatch;
        found   : List[Int]
        required: Option[?]

scala> Option(1).flatMap
final def flatMap[B](f: Int => Option[B]): Option[B]

是因为 Option#flatMap 需要一个函数到 Option 并且将 List 转换为 Option 可能没有意义,因为我们会丢失 List 的元素包含多个元素。

正如szeiger解释的

<块引用>

我认为最近的 Option 变化实际上使 for comprehensions 用例更容易理解,因为您不需要隐式 转换了。 Option 可用于任何 flatMap 的 RHS 集合类型,因为它是 IterableOnce(但不是相反的 因为 Option#flatMap 的 RHS 需要 Option)。