How do you use scalaz.WriterT for logging?
Share
Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.
Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.
Lost your password? Please enter your email address. You will receive a link and will create a new password via email.
Please briefly explain why you feel this question should be reported.
Please briefly explain why you feel this answer should be reported.
Please briefly explain why you feel this user should be reported.
About monad transformers
This is a very short introduction. You may find more information on haskellwiki or this great slide by @jrwest.
Monads don’t compose, meaning that if you have a monad
A[_]and a monadB[_], thenA[B[_]]can not be derived automatically. However in most cases this can be achieved by having a so-called monad transformer for a given monad.If we have monad transformer
BTfor monadB, then we can compose a new monadA[B[_]]for any monadA. That’s right, by usingBT, we can put theBinsideA.Monad transformer usage in scalaz
The following assumes scalaz 7, since frankly I didn’t use monad transformers with scalaz 6.
A monad transformer
MTtakes two type parameters, the first is the wrapper (outside) monad, the second is the actual data type at the bottom of the monad stack. Note: It may take more type parameters, but those are not related to the transformer-ness, but rather specific for that given monad (like the logged type of aWriter, or the error type of aValidation).So if we have a
List[Option[A]]which we would like to treat as a single composed monad, then we needOptionT[List, A]. If we haveOption[List[A]], we needListT[Option, A].How to get there? If we have the non-transformer value, we can usually just wrap it with
MT.applyto get the value inside the transformer. To get back from the transformed form to normal, we usually call.runon the transformed value.So
val a: OptionT[List, Int] = OptionT[List, Int](List(some(1))andval b: List[Option[Int]] = a.runare the same data, just the representation is different.It was suggested by Tony Morris that is best to go into the transformed version as early as possible and use that as long as possible.
Note: Composing multiple monads using transformers yields a transformer stack with types just the opposite order as the normal data type. So a normal
List[Option[Validation[E, A]]]would look something liketype ListOptionValidation[+E, +A] = ValidationT[({type l[+a] = OptionT[List, a]})#l, E, A]Update: As of scalaz 7.0.0-M2,
Validationis (correctly) not a Monad and soValidationTdoesn’t exist. UseEitherTinstead.Using WriterT for logging
Based on your need, you can use the
WriterTwithout any particular outer monad (in this case in the background it will use theIdmonad which doesn’t do anything), or can put the logging inside a monad, or put a monad inside the logging.First case, simple logging
We import the
listMonoidinstance, since it also provides theSemigroup[List]instance. It is needed sinceWriterTneeds the log type to be a semigroup in order to be able to combine the log values.Second case, logging inside a monad
Here we chose the
Optionmonad for simplicity.With this approach, since the logging is inside the
Optionmonad, if any of the bound options isNone, we would just get aNoneresult without any logs.Note:
x.point[Option]is the same in effect asSome(x), but may help to generalize the code better. Not lethal just did it that way for now.Third option, logging outside of a monad
Here we use
OptionTto put theOptionmonad inside theWriter. One of the calculations isNoneto show that even in this case logs are preserved.Final remarks
In these examples
List[String]was used as the log type. However usingStringis hardly ever the best way, just some convention forced on us by logging frameworks. It would be better to define a custom log ADT for example, and if needed to output, convert it to string as late as possible. This way you could serialize the log’s ADT and easily analyse it later programmatically (instead of parsing strings).WriterThas a host of useful methods to work with to ease logging, check out the source. For example given aw: WriterT[...], you may add a new log entry usingw :++> List("other event"), or even log using the currently held value usingw :++>> ((v) => List("the result is " + v)), etc.There are many explicit and longish code (types, calls) in the examples. As always, these are for clarity, refactor them in your code by extracting common types and ops.