Несоответствие типа Scala Для понимания

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

Ответ 1

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

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

Ответ 2

Легкий наконечник для запоминания, поскольку в этом случае попытки попытаться вернуть тип коллекции первого генератора 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; вы могли бы написать это и получить 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. Таким образом, вы не можете комбинировать опцию со списком. Для примеров других вещей, которые могут быть M, посмотрите подклассы этого признака.

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

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

Ответ 3

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