2012-08-14 8 views

risposta

32

Chi trasformatori monade

Questa è una breve introduzione. È possibile trovare ulteriori informazioni su haskellwiki o questo great slide by @jrwest.

Monadi non compongo, il che significa che se si dispone di una monade A[_] e una monade B[_], quindi A[B[_]] non può essere derivato automaticamente. Tuttavia nella maggior parte dei casi questo può essere ottenuto avendo un cosiddetto trasformatore monad per una data monade.

Se abbiamo monade trasformatore BT per monade B, allora possiamo comporre un nuovo monade A[B[_]] per ogni monadeA. Esatto, utilizzando BT, possiamo inserire lo B all'interno di A.

Monade utilizzo trasformatore scalaz

seguito si presuppone scalaz 7 poiché francamente non ho usato trasformatori monade con scalaz 6.

Un trasformatore monod MT accetta due parametri di tipo, il primo è il monad wrapper (esterno), il secondo è il tipo di dati effettivo nella parte inferiore della pila monad. Nota: potrebbero essere necessari più parametri di tipo, ma quelli non sono correlati al trasformatore, ma piuttosto specifici per quella data monade (come il tipo registrato di uno Writer o il tipo di errore di uno Validation).

Quindi se abbiamo un List[Option[A]] che vorremmo considerare come una monade composta singola, allora abbiamo bisogno di OptionT[List, A]. Se abbiamo Option[List[A]], abbiamo bisogno di ListT[Option, A].

Come arrivare? Se abbiamo il valore di non-trasformatore, di solito lo possiamo avvolgere con MT.apply per ottenere il valore all'interno del trasformatore. Per tornare dalla forma trasformata alla normalità, di solito chiamiamo .run sul valore trasformato.

Quindi val a: OptionT[List, Int] = OptionT[List, Int](List(some(1)) e val b: List[Option[Int]] = a.run sono gli stessi dati, solo la rappresentazione è diversa.

È stato suggerito da Tony Morris che è meglio entrare nella versione trasformata il prima possibile e utilizzarla il più a lungo possibile.

Nota: la composizione di più monadi usando trasformatori produce uno stack di trasformatori con tipi dell'ordine opposto al normale tipo di dati. Quindi un normale List[Option[Validation[E, A]]] sarebbe simile type ListOptionValidation[+E, +A] = ValidationT[({type l[+a] = OptionT[List, a]})#l, E, A]

Aggiornamento: Come di scalaz 7.0.0-M2, Validation è (giustamente) non è una monade e così ValidationT non esiste. Utilizzare invece EitherT.

Utilizzando WriterT per l'accesso

base alle proprie esigenze, è possibile utilizzare il WriterT senza particolari monade esterno (in questo caso in sottofondo si utilizzerà il Id Monade, che non fa nulla) , o può mettere il log all'interno di una monade, o mettere una monade all'interno del logging.

Primo caso, semplice registrazione

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) 

Importiamo l'istanza listMonoid, in quanto prevede anche l'istanza Semigroup[List]. È necessario dal momento che WriterT richiede che il tipo di registro sia un semigruppo per poter combinare i valori del registro.

Secondo caso, la registrazione all'interno di una monade

Qui abbiamo scelto monade Option per semplicità.

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 questo approccio, dal momento che la registrazione è all'interno del Option Monade, se una delle opzioni legato viene None, ci sarebbe solo ottenere un risultato None, senza alcun registro.

Nota: x.point[Option] è lo stesso di Some(x), ma può aiutare a generalizzare meglio il codice. Per ora non è letale.

Terza opzione, la registrazione al di fuori di una monade

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) 

Qui usiamo OptionT di mettere il Option Monade all'interno del Writer. Uno dei calcoli è None per mostrare che anche in questo caso i registri sono conservati.

Osservazioni conclusive

In questi esempi List[String] è stato utilizzato come il tipo di registro. Tuttavia, l'utilizzo di String non è quasi mai il modo migliore, solo alcune convenzioni costrette su di noi registrando i framework. Sarebbe meglio definire un log personalizzato ADT, ad esempio, e se necessario per l'output, convertirlo in stringa il più tardi possibile. In questo modo è possibile serializzare l'ADT del registro e analizzarlo facilmente in seguito in modo programmatico (anziché analizzare le stringhe).

WriterT ha una serie di metodi utili con cui semplificare la registrazione, controllare la fonte. Per esempio dato un w: WriterT[...], è possibile aggiungere una nuova voce di registro utilizzando w :++> List("other event"), o anche accedere utilizzando il valore attualmente detenuta tramite w :++>> ((v) => List("the result is " + v)), ecc

Ci sono molti codici espliciti e piuttosto lunga (tipi, chiamate) negli esempi. Come sempre, questi sono per chiarezza, li rifattori nel codice estraendo tipi e operazioni comuni.

+0

Nota: Esiste un'imminente classe di tipi "MonadWriter' nella testa di scalaz-seven. Vale la pena tenerlo d'occhio. – ron

+0

Vedere 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

Mi ricordo di vederlo come gist (twittato da @puffnfresh) https://gist.github.com/3345722 – ron