在 for 理解中将选项与列表组合会导致类型不匹配,具体取决于顺序

发布于 2024-10-12 11:40:57 字数 603 浏览 2 评论 0原文

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

Why does this construction cause a Type Mismatch error in 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)

If I switch the Some with the List it compiles fine:

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

This also works fine:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

溺深海 2024-10-19 11:40:57

For 推导式会转换为对 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 方法调用。由于 List 上的 flatMap 返回另一个 List,因此 for 理解的结果当然是一个 List。 (这对我来说是新的:因为理解并不总是产生流,甚至不一定是 Seq 。)

现在,看看 flatMap 是如何声明的选项

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 的子类型。

For comprehensions are converted into calls to the map or flatMap method. For example this one:

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

becomes that:

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

Therefore, the first loop value (in this case, List(1)) will receive the flatMap method call. Since flatMap on a List returns another List, the result of the for comprehension will of course be a List. (This was new to me: For comprehensions don't always result in streams, not even necessarily in Seqs.)

Now, take a look at how flatMap is declared in Option:

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

Keep this in mind. Let's see how the erroneous for comprehension (the one with Some(1)) gets converted to a sequence of map calls:

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

Now, it's easy to see that the parameter of the flatMap call is something that returns a List, but not an Option, as required.

In order to fix the thing, you can do the following:

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

That compiles just fine. It is worth noting that Option is not a subtype of Seq, as is often assumed.

你对谁都笑 2024-10-19 11:40:57

一个容易记住的提示是,理解将尝试返回第一个生成器的集合类型,在本例中为 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 可以是选项或列表。一般来说,它必须是相同的类型M。所以你不能将 Option 与 List 结合起来。有关可以是 M 的其他内容的示例,请参阅 此特征的子类

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

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

An easy tip to remember, for comprehensions will try to return the type of the collection of the first generator, Option[Int] in this case. So, if you start with Some(1) you should expect a result of Option[T].

If you want a result of List type, you should start with a List generator.

Why have this restriction and not assume you'll always want some sort of sequence? You can have a situation where it makes sense to return Option. Maybe you have an Option[Int] that you want to combine with something to get a Option[List[Int]], say with the following function: (i:Int) => if (i > 0) List.range(0, i) else None; you could then write this and get None when things don't "make sense":

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

How for comprehensions are expanded in the general case are in fact a fairly general mechanism to combine an object of type M[T] with a function (T) => M[U] to get an object of type M[U]. In your example, M can be Option or List. In general it has to be the same type M. So you can't combine Option with List. For examples of other things that can be M, look at subclasses of this trait.

Why did combining List[T] with (T) => Option[T] work though when you started with the List? In this case the library use a more general type where it makes sense. So you can combine List with Traversable and there is an implicit conversion from Option to Traversable.

The bottom line is this: think about what type you want the expression to return and start with that type as the first generator. Wrap it in that type if necessary.

初心 2024-10-19 11:40:57

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

It probably has something to do with Option not being an Iterable. The implicit Option.option2Iterable will handle the case where compiler is expecting second to be an Iterable. I expect that the compiler magic is different depending on the type of the loop variable.

花期渐远 2024-10-19 11:40:57

我总是发现这很有帮助:

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)

I always found this helpful:

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)
眼眸 2024-10-19 11:40:57

由于 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 

因此上面的理解将脱糖为类似于

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 理解
用例更容易理解,因为您不需要隐式
不再转换。 Option 可以用在任何 flatMap 的 RHS 上
集合类型,因为它是 IterableOnce (但不是相反
因为 Option#flatMap 的 RHS 需要 Option)。

Since Scala 2.13 Option was made IterableOnce

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

so the following for comprehension works without the use of option2Iterable implicit conversion

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]

where we see List#flatMap takes a function to IterableOnce. Desugaring above for comprehension we get something like

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

which show the absence of the implicit conversion.

However in Scala 2.12 and before Option was not a traversable/iterable entity

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

so the above for comprehension would desugar to something like

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

where we see the implicit conversion.

The reason it does not work the other way around where for comprehension begins with Option and then we try to chain a 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]

is because Option#flatMap takes a function to Option and converting a List to Option probably does not make sense because we would lose elements for Lists with more than one element.

As szeiger explains

I think the recent Option changes actually make the for comprehensions
use case easier to understand because you do not need an implicit
conversion anymore. Option can be used on the RHS of a flatMap of any
collection type because it is IterableOnce (but not the opposite
because the RHS of Option#flatMap requires Option).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文