Come si usa scalaz.WriterT per la registrazione?Come si usa scalaz.WriterT per l'accesso a un'espressione for?
risposta
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.
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")
Mi ricordo di vederlo come gist (twittato da @puffnfresh) https://gist.github.com/3345722 – ron
- 1. Come si usa unittest.TestResult?
- 2. Come si usa node-mongodb-native per connettersi a Heroku?
- 3. Come si usa TTStyledTextLabel?
- 4. Come si usa AVAssetWriter?
- 5. Come si usa gdb?
- 6. ViewDragHelper: come si usa?
- 7. Usa strtok in un ciclo for
- 8. Come si usa CATransform3DMakeRotation?
- 9. Come si usa Form.ShowDialog?
- 10. Come si usa Resources.getFraction()?
- 11. Come si usa cx_freeze?
- 12. Oauth 2.0 Android usa Spring-For-Android
- 13. Come si usa strdup?
- 14. Proiezioni.Conditional - Come si usa?
- 15. Come si usa layoutIfNeeded?
- 16. IRequiresSessionState - come si usa?
- 17. come si usa ensure_csrf_cookie?
- 18. Come si usa class_eval?
- 19. Come si usa strcasestr()?
- 20. CakeEmail - Come si usa?
- 21. Come si usa setsockopt (SO_REUSEADDR)?
- 22. indirizzo jQuery come si usa?
- 23. Come si usa Drools Planner?
- 24. Per che cosa si usa sigaddset?
- 25. Come si usa Dispatcher.BeginInvoke correttamente?
- 26. Come si usa magrittr :: inset()?
- 27. Come si usa jQuery (elementArray)?
- 28. Come si usa boost bcp?
- 29. Come si usa popover da Twitter Bootstrap per visualizzare un'immagine?
- 30. JPA @Version: come si usa?
Nota: Esiste un'imminente classe di tipi "MonadWriter' nella testa di scalaz-seven. Vale la pena tenerlo d'occhio. – ron
Vedere https: // github.com/scalaz/scalaz/pull/128 – ron