scala future for-comprehension

Future[Option] en Scala for-understandhensions



for-comprehension (5)

¿Qué comportamiento le gustaría que ocurra en el caso de que la Option[School] sea None ? ¿Le gustaría que el futuro fracase? ¿Con qué tipo de excepción? ¿Le gustaría que nunca se complete? (Eso suena como una mala idea).

De todos modos, la cláusula if en una expresión forzada desaparece a una llamada al método de filter . El contrato en Future#filter es por lo tanto:

Si el futuro actual contiene un valor que satisface el predicado, el nuevo futuro también mantendrá ese valor. De lo contrario, el futuro resultante fallará con una NoSuchElementException.

Pero espera:

scala> None.get java.util.NoSuchElementException: None.get

Como puede ver, None.get devuelve exactamente lo mismo.

Por lo tanto, deshacerse del if sid.isDefined debería funcionar, y esto debería devolver un resultado razonable:

val schoolFuture = for { ud <- userStore.getUserDetails(user.userId) sid = ud.right.toOption.flatMap(_.schoolId) s <- schoolStore.getSchool(sid.get) } yield s

Tenga en cuenta que el resultado de schoolFuture puede ser en instancia de scala.util.Failure[NoSuchElementException] . Pero no ha descrito qué otro comportamiento le gustaría.

Tengo dos funciones que devuelven futuros. Estoy tratando de alimentar un resultado modificado de la primera función con la otra utilizando una comprensión de rendimiento.

Este enfoque funciona:

val schoolFuture = for { ud <- userStore.getUserDetails(user.userId) sid = ud.right.toOption.flatMap(_.schoolId) s <- schoolStore.getSchool(sid.get) if sid.isDefined } yield s

Sin embargo, no estoy contento con tener el "si" allí, parece que debería poder usar un mapa en su lugar.

Pero cuando intento con un mapa:

val schoolFuture: Future[Option[School]] = for { ud <- userStore.getUserDetails(user.userId) sid = ud.right.toOption.flatMap(_.schoolId) s <- sid.map(schoolStore.getSchool(_)) } yield s

Me sale un error de compilación:

[error] found : Option[scala.concurrent.Future[Option[School]]] [error] required: scala.concurrent.Future[Option[School]] [error] s <- sid.map(schoolStore.getSchool(_))

He jugado con algunas variaciones, pero no he encontrado nada atractivo que funcione. ¿Alguien puede sugerir una mejor comprensión y / o explicar qué pasa con mi segundo ejemplo?

Aquí hay un ejemplo ejecutable mínimo pero completo con Scala 2.10:

import concurrent.{Future, Promise} case class User(userId: Int) case class UserDetails(userId: Int, schoolId: Option[Int]) case class School(schoolId: Int, name: String) trait Error class UserStore { def getUserDetails(userId: Int): Future[Either[Error, UserDetails]] = Promise.successful(Right(UserDetails(1, Some(1)))).future } class SchoolStore { def getSchool(schoolId: Int): Future[Option[School]] = Promise.successful(Option(School(1, "Big School"))).future } object Demo { import concurrent.ExecutionContext.Implicits.global val userStore = new UserStore val schoolStore = new SchoolStore val user = User(1) val schoolFuture: Future[Option[School]] = for { ud <- userStore.getUserDetails(user.userId) sid = ud.right.toOption.flatMap(_.schoolId) s <- sid.map(schoolStore.getSchool(_)) } yield s }


(¡Editado para dar una respuesta correcta!)

La clave aquí es que Future y Option no componen adentro porque no hay las firmas de flatMap correctas. Como recordatorio, para los azúcares similares:

for ( x0 <- c0; w1 = d1; x1 <- c1 if p1; ... ; xN <- cN) yield f c0.flatMap{ x0 => val w1 = d1 c1.filter(x1 => p1).flatMap{ x1 => ... cN.map(xN => f) ... } }

(donde cualquier instrucción if arroja un filter en la cadena - He dado solo un ejemplo - y las declaraciones iguales simplemente establecen variables antes de la siguiente parte de la cadena). Como solo se puede flatMap otros Future s, cada instrucción c0 , c1 , ... excepto la última, es mejor que produzca un Future .

Ahora, getUserDetails y getSchool producen Futures , pero sid es una Option , por lo que no podemos ponerlo en el lado derecho de un <- . Desafortunadamente, no hay una manera limpia de hacerlo. Si o es una opción, podemos

o.map(Future.successful).getOrElse(Future.failed(new Exception))

convertir una Option en un Future ya completado. Asi que

for { ud <- userStore.getUserDetails(user.userId) // RHS is a Future[Either[...]] sid = ud.right.toOption.flatMap(_.schoolId) // RHS is an Option[Int] fid <- sid.map(Future.successful).getOrElse(Future.failed(new Exception)) // RHS is Future[Int] s <- schoolStore.getSchool(fid) } yield s

hará el truco ¿Es eso mejor de lo que tienes? Dudoso. Pero si tu

implicit class OptionIsFuture[A](val option: Option[A]) extends AnyVal { def future = option.map(Future.successful).getOrElse(Future.failed(new Exception)) }

entonces, de repente, la comprensión forzosa parece razonable de nuevo:

for { ud <- userStore.getUserDetails(user.userId) sid <- ud.right.toOption.flatMap(_.schoolId).future s <- schoolStore.getSchool(sid) } yield s

¿Es esta la mejor manera de escribir este código? Probablemente no; depende de convertir una None en una excepción simplemente porque no sabe qué otra cosa hacer en ese momento. Esto es difícil de solucionar debido a las decisiones de diseño de Future ; Sugeriría que tu código original (que invoca un filtro) sea al menos una buena forma de hacerlo.


Es más fácil usar https://github.com/qifun/stateless-future o https://github.com/scala/async para hacer la transformación A-Normal-Form .


Hemos hecho una pequeña envoltura en Future [Option [T]] que actúa como una mónada (nadie incluso verificó ninguna ley de mónadas, pero hay un mapa, flatMap, foreach, filter, etc.) - MaybeLater . Se comporta mucho más que una opción asíncrona.

Hay un montón de código maloliente, pero tal vez sea útil al menos como ejemplo. Por cierto: hay muchas preguntas abiertas ( here por ejemplo)


Esta respuesta a una pregunta similar sobre Promise[Option[A]] podría ayudar. Simplemente sustituya Future for Promise .

Estoy inferir los siguientes tipos para getUserDetails y getSchool de su pregunta:

getUserDetails: UserID => Future[Either[??, UserDetails]] getSchool: SchoolID => Future[Option[School]]

Como ignora el valor de falla de Either , transformándolo en una Option , efectivamente tiene dos valores de tipo A => Future[Option[B]] .

Una vez que tenga una instancia de Monad para Future (puede haber una en scalaz , o podría escribir la suya como en la respuesta que he vinculado), aplicar el transformador OptionT a su problema se vería así:

for { ud <- optionT(getUserDetails(user.userID) map (_.right.toOption)) sid <- optionT(Future.successful(ud.schoolID)) s <- optionT(getSchool(sid)) } yield s

Tenga en cuenta que, para mantener los tipos compatibles, ud.schoolID está ud.schoolID en un (ya completado) futuro.

El resultado de esto para la comprensión tendría el tipo OptionT[Future, SchoolID] . Puede extraer un valor de tipo Future[Option[SchoolID]] con el método de run del transformador.