Несоответствие типов в Scala для понимания - PullRequest
76 голосов
/ 18 января 2011

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

Если я переключаю некоторые из них со списком, он прекрасно компилируется:

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)

Ответы [ 4 ]

113 голосов
/ 18 января 2011

Для понимания преобразуются в вызовы метода 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, как это часто предполагается.

31 голосов
/ 18 января 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 с List.Для примеров других вещей, которые могут быть M, посмотрите на подклассы этой черты .

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

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

4 голосов
/ 18 января 2011

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

0 голосов
/ 19 июня 2018

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

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)
...