¿Por qué esta construcción causa un error de no coincidencia de tipos en 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)
Si cambio Some con la Lista, se compila bien:
for (first <- List(1,2,3); second <- Some(1)) yield (first,second)
res41: List[(Int, Int)] = List((1,1), (2,1), (3,1))
Esto también funciona bien:
for (first <- Some(1); second <- Some(2)) yield (first,second)
scala
for-loop
type-mismatch
for-comprehension
scala-option
Felipe Kamakura
fuente
fuente
Respuestas:
Porque las comprensiones se convierten en llamadas al método
map
oflatMap
. Por ejemplo este:for(x <- List(1) ; y <- List(1,2,3)) yield (x,y)
se convierte en eso:
List(1).flatMap(x => List(1,2,3).map(y => (x,y)))
Por lo tanto, el primer valor de bucle (en este caso
List(1)
) recibirá laflatMap
llamada al método. Dado queflatMap
en aList
devuelve otroList
, el resultado de la comprensión será, por supuesto, aList
. (Esto era nuevo para mí: porque las comprensiones no siempre resultan en flujos, ni siquiera necesariamente enSeq
s).Ahora, eche un vistazo a cómo
flatMap
se declara enOption
:def flatMap [B] (f: (A) ⇒ Option[B]) : Option[B]
Mantén esto en mente. Veamos cómo el error de comprensión (el que tiene
Some(1)
) se convierte en una secuencia de llamadas de mapa:Some(1).flatMap(x => List(1,2,3).map(y => (x, y)))
Ahora, es fácil ver que el parámetro de la
flatMap
llamada es algo que devuelve unList
, pero no unOption
, como se requiere.Para solucionar el problema, puede hacer lo siguiente:
for(x <- Some(1).toSeq ; y <- List(1,2,3)) yield (x, y)
Eso se compila muy bien. Vale la pena señalar que
Option
no es un subtipo deSeq
, como a menudo se supone.fuente
Un consejo fácil de recordar, para las comprensiones tratará de devolver el tipo de colección del primer generador, Option [Int] en este caso. Por lo tanto, si comienza con Some (1) , debe esperar un resultado de Option [T].
Si desea un resultado del tipo Lista , debe comenzar con un generador de listas.
¿Por qué tener esta restricción y no asumir que siempre querrá algún tipo de secuencia? Puede tener una situación en la que tenga sentido regresar
Option
. Tal vez tenga unOption[Int]
que desee combinar con algo para obtener unOption[List[Int]]
, digamos con la siguiente función(i:Int) => if (i > 0) List.range(0, i) else None
:; luego podría escribir esto y obtener Ninguno cuando las cosas no "tengan 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
La forma en que se expanden las comprensiones en el caso general es de hecho un mecanismo bastante general para combinar un objeto de tipo
M[T]
con una función(T) => M[U]
para obtener un objeto de tipoM[U]
. En su ejemplo, M puede ser Opción o Lista. En general, tiene que ser del mismo tipoM
. Por lo tanto, no puede combinar Option con List. Para ver ejemplos de otras cosas que pueden serM
, observe las subclases de este rasgo .¿Por qué combinarlo
List[T]
con el(T) => Option[T]
trabajo cuando empezaste con la Lista? En este caso, la biblioteca usa un tipo más general donde tiene sentido. Por lo tanto, puede combinar List con Traversable y hay una conversión implícita de Option a Traversable.La conclusión es la siguiente: piense qué tipo desea que devuelva la expresión y comience con ese tipo como primer generador. Envuélvalo en ese tipo si es necesario.
fuente
for
sintaxis regular haga este tipo de desugaring functor / monádico. ¿Por qué no tener métodos con nombres diferentes para el mapeo de funciones / mónadas, comofmap
, etc., y reservar lafor
sintaxis para tener un comportamiento extremadamente simple que coincida con las expectativas provenientes de prácticamente cualquier otro lenguaje de programación convencional?Probablemente tenga algo que ver con que Option no sea un Iterable. El implícito
Option.option2Iterable
manejará el caso en el que el compilador espera que el segundo sea un Iterable. Espero que la magia del compilador sea diferente según el tipo de variable de ciclo.fuente
Siempre encontré esto ú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 <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)
fuente