Tipo incompatibilidade no Scala para compreensão

Por que essa construção causa um erro de incompatibilidade de tipo no Scala?

for (first <- Some(1); second <- List(1,2,3)) yield (first,second) :6: error: type mismatch; found : List[(Int, Int)] required: Option[?] for (first <- Some(1); second <- List(1,2,3)) yield (first,second) 

Se eu mudar o Some com a List, compila bem:

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

Isso também funciona bem:

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

Para as compreensões são convertidas em chamadas para o map ou método flatMap . Por exemplo este:

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

torna-se isso:

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

Portanto, o primeiro valor de loop (neste caso, List(1) ) receberá a chamada de método flatMap . Uma vez que o flatMap em uma List retorna outra List , o resultado da compreensão será, naturalmente, uma List . (Isso era novidade para mim: para as compreensões nem sempre resultam em streams, nem necessariamente em Seq s.)

Agora, veja como o flatMap é declarado na Option :

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

Mantenha isso em mente. Vamos ver como o errôneo para compreensão (aquele com Some(1) ) é convertido em uma sequência de chamadas de mapa:

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

Agora, é fácil ver que o parâmetro da chamada flatMap é algo que retorna uma List , mas não uma Option , conforme necessário.

Para consertar a coisa, você pode fazer o seguinte:

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

Isso compila muito bem. Vale a pena notar que Option não é um subtipo de Seq , como é frequentemente assumido.

Uma dica fácil de lembrar, para as compreensões tentará retornar o tipo da coleção do primeiro gerador, Opção [Int] neste caso. Então, se você começar com Some (1), você deve esperar um resultado da Option [T].

Se você quiser um resultado do tipo List , você deve começar com um gerador de lista.

Por que ter essa restrição e não assumir que você sempre desejará algum tipo de sequência? Você pode ter uma situação em que faz sentido retornar Option . Talvez você tenha uma Option[Int] que deseja combinar com algo para obter uma Option[List[Int]] , digamos com a seguinte function: (i:Int) => if (i > 0) List.range(0, i) else None ; você poderia então escrever isto e obter Nenhum quando as coisas não “fizerem sentido”:

 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 

Como as compreensões são expandidas no caso geral são, de fato, um mecanismo bastante geral para combinar um object do tipo M[T] com uma function (T) => M[U] para obter um object do tipo M[U] . No seu exemplo, M pode ser Opção ou Lista. Em geral, tem que ser do mesmo tipo M Então você não pode combinar opção com lista. Para exemplos de outras coisas que podem ser M , veja subclasss desse traço .

Por que a combinação de List[T] com (T) => Option[T] funcionou quando você começou com a List? Nesse caso, a biblioteca usa um tipo mais geral, onde faz sentido. Então você pode combinar List com Traversable e há uma conversão implícita de Option para Traversable.

A conclusão é a seguinte: pense em qual tipo você deseja que a expressão retorne e comece com esse tipo como o primeiro gerador. Envolva-o nesse tipo, se necessário.

Provavelmente tem algo a ver com a Opção de não ser um Iterable. O implícito Option.option2Iterable manipulará o caso em que o compilador espera que o segundo seja Iterable. Eu espero que a magia do compilador seja diferente dependendo do tipo da variável de loop.

Eu sempre achei isso útil:

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