2011-01-18 11 views
64

Perché questa costruzione causa un errore di mancata corrispondenza di tipo in Scala?Tipo non corrispondente su Scala per la comprensione

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) 

Se posso passare il Alcuni con la Lista compila bene:

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

Questo funziona anche bene:

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

Quale risultato ti aspettavi che Scala restituisse nell'esempio negativo? –

+0

Quando stavo scrivendo pensavo di ottenere un'opzione [Elenco [(Int, Int)]]. –

risposta

99

Per comprensioni sono convertite in chiamate al metodo map o flatMap . Per esempio questo:

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

diventa che:

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

Pertanto, il primo valore loop (in questo caso, List(1)) riceverà la chiamata flatMap metodo. Dal flatMap su un List restituisce un altro List, il risultato della comprensione sarà ovviamente List. (Questo è stato nuovo per me: per comprensioni non sempre si traducono in corsi d'acqua, neanche necessariamente in Seq s.)

Ora, date un'occhiata a come flatMap è dichiarata in Option:

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

Tenere questo in mente. Vediamo come l'erronea per la comprensione (quello con Some(1)) viene convertito in una sequenza di mappa chiama:

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

Ora, è facile vedere che il parametro della chiamata flatMap è qualcosa che restituisce un List, ma non è un Option, come richiesto.

Al fine di risolvere la cosa, è possibile effettuare le seguenti operazioni:

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

che compila bene. Vale la pena notare che Option non è un sottotipo di Seq, come spesso si presume.

4

Probabilmente ha qualcosa a che fare con Option non essendo un Iterable. L'implicito Option.option2Iterable gestirà il caso in cui il compilatore si aspetta che il secondo sia un Iterable. Mi aspetto che la magia del compilatore sia diversa a seconda del tipo di variabile del ciclo.

24

Un semplice suggerimento da ricordare, per le considerazioni tenterà di restituire il tipo di raccolta del primo generatore, Opzione [Int] in questo caso. Quindi, se inizi con Alcuni (1) dovresti aspettarti un risultato dell'opzione [T].

Se si desidera un risultato del tipo List, è necessario iniziare con un generatore di elenchi.

Perché questa restrizione non presuppone che vorrete sempre una sequenza? Puoi avere una situazione in cui ha senso restituire Option.Forse hai un Option[Int] che vuoi combinare con qualcosa per ottenere un Option[List[Int]], ad esempio con la seguente funzione: (i:Int) => if (i > 0) List.range(0, i) else None; si potrebbe quindi scrivere questo e ottenere Nessuno quando le cose non "hanno senso":

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 

Come espressioni for sono espansi nel caso generale sono in realtà un meccanismo abbastanza generale di combinare un oggetto di tipo M[T] con una funzione (T) => M[U] per ottenere un oggetto di tipo M[U]. Nel tuo esempio, M può essere Opzione o Elenco. In generale deve essere lo stesso tipo M. Quindi non è possibile combinare l'opzione con Elenco. Per esempi di altre cose che possono essere M, consulta subclasses of this trait.

Perché la combinazione di List[T] con (T) => Option[T] funziona anche se si è iniziato con l'Elenco? In questo caso la libreria usa un tipo più generale dove ha senso. Quindi puoi combinare Lista con Traversabile e c'è una conversione implicita da Opzione a Traversabile.

La linea di fondo è questa: pensate a che tipo volete che l'espressione ritorni e inizi con quel tipo come primo generatore. Avvolgilo in quel tipo se necessario.

Problemi correlati