Несоответствие типа 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. Я ожидаю, что магия компилятора отличается в зависимости от типа переменной цикла.