Tipo non corrispondente a Scala per la comprensione
-
12-10-2019 - |
Domanda
Perché questa costruzione causa un errore di tipo non corrispondente a 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)
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)
Soluzione
Per comprensioni sono convertiti in chiamate al metodo map
o flatMap
. Per esempio questa:
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 metodo flatMap
. Dal momento che flatMap
su un List
ritorna un'altra List
, il risultato della per la comprensione sarà ovviamente un 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 chiamate mappa:
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 suppone.
Altri suggerimenti
Una punta facile da ricordare, espressioni for cercherà di restituire il tipo della collezione del primo generatore, Riservato [Int] in questo caso. Quindi, se si inizia con Alcuni (1) si dovrebbe aspettare un risultato di Opzione [T].
Se si desidera un risultato di List tipo, si dovrebbe iniziare con un generatore di lista.
Perché questa restrizione e non assume sarai sempre desidera una sorta di sequenza? Si può avere una situazione in cui ha senso Option
ritorno. Forse avete un Option[Int]
che si desidera combinare con qualcosa per ottenere un Option[List[Int]]
, dice 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 "ha 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 per comprehensions sono espansi nel caso generale sono infatti un meccanismo abbastanza generale di combinare un oggetto di tipo M[T]
con funzione (T) => M[U]
per ottenere un oggetto di tipo M[U]
. Nel tuo esempio, M può essere Opzione o List. In generale deve essere dello stesso tipo M
. Quindi non è possibile combinare con opzione List. Per esempi di altre cose che possono essere M
, un'occhiata a sottoclassi di questo tratto .
Perché unisce List[T]
con il lavoro (T) => Option[T]
anche se quando si è iniziato con la lista? In questo caso la libreria di utilizzare un tipo più generale in cui ha senso. Così si può combinare con Lista Traversable e c'è una conversione implicita da Opzione per Traversable.
La linea di fondo è questo: pensare a che tipo si desidera che l'espressione di tornare e iniziare con quel tipo, come il primo generatore. Avvolgerlo in quel tipo, se necessario.
Probabilmente ha qualcosa a che fare con l'opzione non essendo un Iterable. L'implicita Option.option2Iterable
gestirà il caso in cui il compilatore si aspetta secondo per essere un Iterable. Mi aspetto che la magia compilatore è diverso a seconda del tipo di variabile del ciclo.
Ho sempre trovato questa utile:
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)