2012-08-14 19 views

Respuesta

32

Sobre transformadores monad

Esta es una introducción muy breve. Puede encontrar más información en haskellwiki o este great slide by @jrwest.

mónadas no componen, lo que significa que si usted tiene una mónada A[_] y una mónada B[_], entonces A[B[_]] no puede ser derivada de forma automática . Sin embargo, en la mayoría de los casos esto se puede lograr teniendo un llamado transformador de mónada para una mónada determinada.

Si tenemos transformador mónada BT para mónada B, entonces podemos componer una nueva mónada A[B[_]] para cualquier mónadaA. Así es, usando BT, podemos poner el B dentro de A.

el uso del transformador Mónada en scalaz

El siguiente supone scalaz 7, ya que francamente no me usar transformadores monad con scalaz 6.

Un transformador de mónada MT toma dos parámetros de tipo, el primero es la mónada de envoltura (externa), el segundo es el tipo de datos real en la parte inferior de la pila de mónadas. Nota: Puede tomar más parámetros de tipo, pero no están relacionados con el transformador, sino que son específicos para esa mónada determinada (como el tipo registrado de Writer, o el tipo de error de Validation).

Así que si tenemos un List[Option[A]] que nos gustaría tratar como una sola mónada compuesta, entonces necesitamos OptionT[List, A]. Si tenemos Option[List[A]], necesitamos ListT[Option, A].

¿Cómo llegar? Si tenemos el valor sin transformador, generalmente podemos envolverlo con MT.apply para obtener el valor dentro del transformador. Para volver de la forma transformada a la normalidad, generalmente llamamos al .run en el valor transformado.

Entonces val a: OptionT[List, Int] = OptionT[List, Int](List(some(1)) y val b: List[Option[Int]] = a.run son los mismos datos, solo que la representación es diferente.

Tony Morris sugirió que es mejor ir a la versión transformada lo antes posible y usarla el mayor tiempo posible.

Nota: Al componer múltiples mónadas utilizando transformadores, se obtiene una pila de transformadores con tipos del orden inverso al del tipo de datos normal. Así que una normal de List[Option[Validation[E, A]]] sería algo como type ListOptionValidation[+E, +A] = ValidationT[({type l[+a] = OptionT[List, a]})#l, E, A]

Actualización: A partir de scalaz 7.0.0-M2, es Validation (correctamente) no es una mónada y así ValidationT no existe. Use EitherT en su lugar.

Usando WriterT para iniciar sesión

En función de su necesidad, puede utilizar la WriterT sin ninguna mónada externa en particular (en este caso, en el fondo se utilizará el Id mónada que no hace nada) , o puede poner el registro dentro de una mónada, o poner una mónada dentro del registro.

primer caso, el registro sencillo

import scalaz.{Writer} 
import scalaz.std.list.listMonoid 
import scalaz._ 

def calc1 = Writer(List("doing calc"), 11) 
def calc2 = Writer(List("doing other"), 22) 

val r = for { 
    a <- calc1 
    b <- calc2 
} yield { 
    a + b 
} 

r.run should be_== (List("doing calc", "doing other"), 33) 

Importamos la instancia listMonoid, ya que también ofrece la instancia Semigroup[List]. Es necesario ya que WriterT necesita que el tipo de registro sea un semigrupo para poder combinar los valores de registro.

segundo caso, el registro dentro de una mónada

Aquí se optó por la mónada Option por simplicidad.

import scalaz.{Writer, WriterT} 
import scalaz.std.list.listMonoid 
import scalaz.std.option.optionInstance 
import scalaz.syntax.pointed._ 

def calc1 = WriterT((List("doing calc") -> 11).point[Option]) 
def calc2 = WriterT((List("doing other") -> 22).point[Option]) 

val r = for { 
    a <- calc1 
    b <- calc2 
} yield { 
    a + b 
} 

r.run should be_== (Some(List("doing calc", "doing other"), 33)) 

Con este enfoque, ya que el registro está dentro de la Option mónada, si alguna de las opciones de cota es None, nos acaba de obtener un resultado None sin ningún tipo de registros.

Nota: x.point[Option] tiene el mismo efecto que Some(x), pero puede ayudar a generalizar mejor el código. No letal solo lo hizo de esa manera por ahora.

tercera opción, el registro fuera de una mónada

import scalaz.{Writer, OptionT} 
import scalaz.std.list.listMonoid 
import scalaz.std.option.optionInstance 
import scalaz.syntax.pointed._ 

type Logger[+A] = WriterT[scalaz.Id.Id, List[String], A] 

def calc1 = OptionT[Logger, Int](Writer(List("doing calc"), Some(11): Option[Int])) 
def calc2 = OptionT[Logger, Int](Writer(List("doing other"), None: Option[Int])) 

val r = for { 
    a <- calc1 
    b <- calc2 
} yield { 
    a + b 
} 

r.run.run should be_== (List("doing calc", "doing other") -> None) 

Aquí se utiliza OptionT poner la mónada Option dentro del Writer. Uno de los cálculos es None para mostrar que incluso en este caso, los registros se conservan.

Observaciones finales

En estos ejemplos List[String] se utilizó como el tipo de registro. Sin embargo, usar String casi nunca es la mejor manera, solo alguna convención forzada por los marcos de trabajo. Sería mejor definir un ADT de registro personalizado, por ejemplo, y si fuera necesario generarlo, conviértalo en cadena lo más tarde posible. De esta forma, podría serializar el ADT del registro y analizarlo fácilmente más tarde mediante programación (en lugar de analizar cadenas).

WriterT tiene una gran cantidad de métodos útiles para trabajar con la facilidad de registro, echa un vistazo a la fuente. Por ejemplo dado un w: WriterT[...], es posible agregar una nueva entrada de registro mediante w :++> List("other event"), o incluso iniciar la sesión utilizando el valor actualmente en manos usando w :++>> ((v) => List("the result is " + v)), etc.

Hay muchos códigos explícita y bien largo (tipos), llamadas en los ejemplos. Como siempre, estos son para mayor claridad, refactorícelos en su código extrayendo tipos y operaciones comunes.

+0

Nota: Hay una próxima clase de tipo 'MonadWriter' en scalaz-seven head. Vale la pena vigilarlo. – ron

+0

Ver https: // github.com/scalaz/scalaz/pull/128 – ron

0
type OptionLogger[A] = WriterT[Option, NonEmptyList[String], A] 

     val two: OptionLogger[Int] = WriterT.put(2.some)("The number two".pure[NonEmptyList]) 
     val hundred: OptionLogger[Int] = WriterT.put(100.some)("One hundred".pure[NonEmptyList]) 

     val twoHundred = for { 
     a <- two 
     b <- hundred 
     } yield a * b 

     twoHundred.value must be equalTo(200.some) 


     val log = twoHundred.written map { _.list } getOrElse List() mkString(" ") 
     log must be equalTo("The number two One hundred") 
+1

Recuerdo ver esto como esencia (tuiteado por @puffnfresh) https://gist.github.com/3345722 – ron

Cuestiones relacionadas