2012-09-03 24 views
19

¡Tengo un juego! 2 para la aplicación Scala que necesita recuperar algunos datos en formato JSON desde un servicio externo.¿Se aplican los transformadores de mónada para obtener JSON de los servicios?

The Play! framework permite realizar solicitudes HTTP de forma asíncrona al envolver la respuesta en un Promise. Promise es una mónada que ajusta un valor que estará disponible en el futuro.

Esto está bien, pero en mi caso lo que obtengo del servicio web es una cadena JSON. Tengo que analizarlo y el análisis puede fallar. Así que tengo que envolver todo lo que obtenga en un Option. El resultado es que muchos de mis métodos están devolviendo Promise[Option[Whatever]]. Es decir, un valor de tipo Whatever que, tal vez, estará disponible más adelante.

Ahora cada vez que tengo que operar sobre tal valor necesito map dos veces. Estaba pensando en el manejo de esta de la siguiente manera:

  • creación de un nuevo tipo, responda Hope[A], que envuelve un Promise[Option[A]]
  • la definición de los métodos pertinentes como map (o quizás debería utilizar foreach y heredar de alguna colección rasgo?) y flatten
  • proporcionan un convertidor implícito entre Promise[Option[A]] y Hope[A].

Es fácil de definir map - la composición de dos funtores es de nuevo un funtor - y flatten puede hacerse de forma explícita en este caso, o cuando la composición de una mónada con Option.

Pero es mi entendimiento limitado de que no necesito reinventar esto: el transformador de mónada existe exactamente para este caso. O, bueno, entonces creo que nunca he usado un transformador de mónada, y este es el punto de la pregunta:

¿Se pueden utilizar los transformadores de mónada en esta situación? ¿Cómo podría utilizarlos realmente?

Respuesta

15

El uso del transformador de la biblioteca Scalaz OptionT, usted debe ser capaz de Gire valores del tipo Promise[Option[A]] en valores del tipo OptionT[Promise, A].

Usando Scalaz 7:

import scalaz.OptionT._ 
val x: OptionT[Promise, Int] = optionT(Promise.pure(Some(123))) 

Para utilizar este valor, por ejemplo para llamar a map o flatMap en él, tendrá que proporcionar una clase de tipos apropiados para Promise (Functor para map, Monad para flatMap).

Dado que Promise es monadic, debería ser posible proporcionar una instancia de Monad[Promise]. (Usted obtendrá Functor y Applicative de forma gratuita, ya que las clases de tipos forman una jerarquía de herencia.) Por ejemplo (nota: no he probado esto!):

implicit val promiseMonad = new Monad[Promise] { 
    def point[A](a: => A): Promise[A] = Promise.pure(a) 
    def bind[A, B](fa: Promise[A])(f: A => Promise[B]): Promise[B] = fa flatMap f 
} 

Como un simple ejemplo, ahora se puede utilizar map en el OptionT[Promise, A], para aplicar una función de tipo A => B al valor en el interior:

def foo[A, B](x: OptionT[Promise, A], f: A => B): OptionT[Promise, B] = x map f 

para recuperar el Promise[Option[A]] valor subyacente de un OptionT[Promise, A], llame al método run.

def bar[A, B](x: Promise[Option[A]], f: A => B): Promise[Option[B]] = 
    optionT(x).map(f).run 

Usted va a obtener más beneficios de utilizar transformadores monad cuando se puede componer de varias operaciones de tipos compatibles, preservando el tipo OptionT[Promise, _] entre las operaciones y recuperar el valor subyacente al final.

Para componer operaciones en una comprensión forzosa, necesitará funciones del tipo A => OptionT[Promise, B].

+0

¡Gracias, parece que esto es exactamente lo que necesito! – Andrea

+0

Lo he probado y todo funciona bien en mi aplicación. Solo hay una cosa extraña: si introduzco un tipo alias 'escriba Hope [A] = Promise [Option [A]]' para simplificar el tipo de devolución de mis funciones, obtengo un error en tiempo de compilación 'java.lang.IllegalArgumentException : transpose requiere que todas las colecciones tengan el mismo tamaño ". ¿Tienes alguna pista de por qué? – Andrea

+0

@Andrea: si usa un parámetro de tipo explícito con 'optionT', el error debería desaparecer. Parece un error de compilación y, si tiene tiempo, valdría la pena consultar [el rastreador de problemas] (https://issues.scala-lang.org/secure/Dashboard.jspa) o hacer una pregunta de seguimiento aquí. –

1

- Eliminado -

edición:

bien, puede simplemente usar el scalaz.OptionT aquí:

val x = optionT(Promise { /* api call */ some("""{ "foo": "bar" }""") }) 
val mapped = x.map(Json.parse).run // run return the resulting Promise[Option[T]] 
+0

Bueno, al escribir 'x map (_ * 2)' en lugar de sus cuatro líneas. Ahora, en este pequeño ejemplo, puede ser de poco valor, pero en casos más complejos, creo que puede aclarar las cosas. – Andrea

+0

actualizó mi respuesta – drexin

+0

Este for-comprensión no se compilará porque 'x' y' opt' no son de tipos compatibles. Necesitarás anidar dos para-comprehensiones. –

Cuestiones relacionadas