Вариант составления со списком в for-понимание дает несоответствие типов в зависимости от порядка

Почему эта конструкция вызывает ошибку несоответствия типов в 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)

person Felipe Kamakura    schedule 18.01.2011    source источник
comment
Какой результат вы ожидали от Scala в неудачном примере?   -  person Daniel C. Sobral    schedule 18.01.2011
comment
Когда я писал это, я думал, что получу Option [List [(Int, Int)]].   -  person Felipe Kamakura    schedule 18.01.2011


Ответы (5)


Ибо понимания преобразуются в вызовы метода map или flatMap. Например, вот этот:

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, результатом для понимания, конечно же, будет List. (Это было в новинку для меня: понимание не всегда приводит к потокам, даже не обязательно к Seq.)

Теперь посмотрим, как flatMap объявлен в Option:

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, как это часто предполагается.

person Madoc    schedule 18.01.2011

Простой совет: для понимания попытается вернуть тип коллекции первого генератора, в данном случае Option [Int]. Итак, если вы начнете с Some (1), вы должны ожидать результата от Option [T].

Если вам нужен результат типа List, вам следует начать с генератора списков.

Зачем вводить это ограничение и не предполагать, что вам всегда будет нужна какая-то последовательность? Может возникнуть ситуация, когда имеет смысл вернуть 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 со списком. Примеры других вещей, которые могут быть M, см. В подклассах этой черты.

Почему же объединение List[T] с (T) => Option[T] работало, когда вы начинали со списком? В этом случае библиотека использует более общий тип, где это имеет смысл. Таким образом, вы можете комбинировать List с Traversable, и есть неявное преобразование из Option в Traversable.

Суть в следующем: подумайте, какой тип вы хотите, чтобы выражение возвращало выражение, и начните с этого типа в качестве первого генератора. При необходимости заверните его в этот вид.

person huynhjl    schedule 18.01.2011
comment
Я бы сказал, что использование обычного синтаксиса for для этого типа функторного / монадического десугарирования - плохой выбор. Почему бы не использовать методы с разными именами для отображения функторов / монад, например fmap и т. Д., И не оставить синтаксис for, чтобы иметь чрезвычайно простое поведение, которое соответствует ожиданиям, исходящим от практически любого другого основного языка программирования? - person ely; 20.08.2018
comment
Вы можете сделать отдельный вид материала fmap / lift столь же универсальным, как вам нравится, без того, чтобы основной оператор потока управления последовательными вычислениями стал очень неожиданным и имел нюансы, связанные с производительностью и т. Д. Все, с чем работает, того не стоит. - person ely; 20.08.2018

Вероятно, это как-то связано с тем, что Option не является Iterable. Неявный _ 1_ будет обрабатывать случай, когда компилятор ожидает, что второй будет Iterable. Я ожидаю, что магия компилятора различается в зависимости от типа переменной цикла.

person sblundy    schedule 18.01.2011

Я всегда находил это полезным:

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)
person user451151    schedule 18.06.2018

Поскольку 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 изменения на самом деле упрощают понимание сценария использования для понимания, потому что вам больше не нужно неявное преобразование. Option может использоваться в правой части flatMap любого типа коллекции, потому что это IterableOnce (но не наоборот, потому что правая часть Option#flatMap требует Option).

person Mario Galic    schedule 10.04.2021