Scala上的类型不匹配以进行理解


81

为什么这种构造会在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)

如果我用列表切换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)

2
您期望Scala在失败的示例中返回什么结果?
Daniel C. Sobral

1
当我写它的时候,我想我会得到一个Option [List [(Int,Int)]]。
镰仓菲利普(Felipe Kamakura)

Answers:


117

对于理解将转换为对maporflatMap方法的调用。例如这个:

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方法调用。由于flatMapList返回另一个List,的的理解,结果当然会是一个List。(这对我来说是新的:因为理解并不总是导致信息流,甚至不一定会导致Seqs。)

现在,看看如何flatMap在中声明Option

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

请记住这一点。让我们看看如何将理解错误(带有的错误Some(1))转换为一系列map调用:

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通常所假定的的子类型。


31

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

如果需要列表类型的结果,则应从列表生成器开始。

为什么要有此限制,而不是假设您总是想要某种顺序?您可能会遇到需要返回的情况Option。也许你有一个Option[Int]你想要的东西结合起来,得到一个Option[List[Int]],用下面的函数说:(i:Int) => if (i > 0) List.range(0, i) else None; 然后,您可以编写此代码,并在事情没有“意义”时得到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

实际上,如何扩展理解力实际上是将类型的对象M[T]与函数组合(T) => M[U]以获得类型的对象的相当通用的机制M[U]。在您的示例中,M可以是Option或List。通常,它必须是相同的类型M。因此,您不能将Option与List结合使用。有关其他可能出现的情况的示例M,请查看此特征的子类

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

底线是:考虑要让表达式返回哪种类型,并以该类型作为第一个生成器开始。如有必要,将其包装为该类型。


我认为使常规for语法执行此类仿函数/单子减法运算是一个错误的设计选择。为什么不为functor / monad映射使用不同名称的方法(如fmap等等),并保留for语法以使其具有极其简单的行为,使其与几乎来自任何其他主流编程语言的期望相匹配?
ely

您可以根据需要将fmap / lift分离为通用的类,而不必使主流的顺序计算控制流语句变得非常令人惊讶,并且会引起细微的性能复杂性等。
ely

4

它可能与Option不是Iterable有关。隐式Option.option2Iterable将处理编译器期望秒为Iterable的情况。我希望编译器的魔术因循环变量的类型而异。


1

我总是觉得这很有帮助:

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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.