[ScaVa->Scala] Scalaz Writer Monad
之前一篇 [ScaVa->Scala] Scala中使用Reader Monad來實現Dependency Injection ,裡面提到了Scalaz中的Reader Monad,有Reader當然也要有個Writer呀!!
使用情境
Writer Monad在scalaz的source code裡的註解是Computations that log a value
假設今天有一個需求,是要進行一個有多個步驟的處理,而這個處理的過程我們需要Log起來,也就是在每個步驟加上對應的記錄,並且在處理完成之後,要能把過程印在畫面上,在許多個步驟在處理時,這些Log要怎麼收集呢?
或許可以寫File或是塞DB啊!要能同時處理多個Request, 就是給不同的id產生不同的file,然後把log都寫進去,最後完成時,再把檔案讀出來(或是查db)回傳出去。但這樣寫File的I/O其實很浪費,用DB的話更浪費了。所以如果資料量小小的,我們存在記憶體裡就好了,但是在這種implement上,怎麼寫比較方便呢?其實用tuple可以做到(第一個放這個步驟處理完真正的回傳值,第二個放對應的描述log),但是每個步驟都要把結果串在一起,寫起來很辛苦,這時候就可以用上Writer Monad。
Writer Monad
Writer
其實是WriterT
的別名(alias),並且指定WriterT中的第一個型態scalaz.Id
是scalaz的Identity Monad。
Writer
type Writer[W, A] = WriterT[Id, W, A]
object Writer {
def apply[W, A](w: W, a: A): WriterT[Id, W, A] = WriterT[Id, W, A]((w, a))
}
WriterT這邊主要裡面有個run
,他就是幫你把這兩個值(一個是你原本的值,另一個是你要寫的log)包在一起成tuple,然後放進Identity Monad,另外就是written和value了,分別是你所write的log,和本來要回傳的value。
WriterT
sealed trait WriterT[F[+_], +W, +A] { self =>
val run: F[(W, A)]
def written(implicit F: Functor[F]): F[W] =
F.map(run)(_._1)
def value(implicit F: Functor[F]): F[A] =
F.map(run)(_._2)
}
舉個例子
我有四個動作:
1. 用名字查出User的Id
2. 用Id查這個User的State
3. 用Id再查User的Age
4. 最後做個Foo的Check
def searchUserId(name:String)=123.set(Vector(s"search user with name:[$name] found id:[123]"))
def getState(id:Int)="alive".set(Vector("state is alive"))
def getAge(id:Int)=18.set(Vector("age is 18"))
def checkFoo(state:String,age:Int)=(age>=18)?"Old Foo".set(Vector(s"age older then 18 is Old Foo."))|"Young Foo".set(Vector(s"age younger than 18 is Young Foo."))
val foo = for{
uid <- searchUserId("Joe")
state <- getState(uid)
age <- getAge(uid)
foo <- checkFoo(state,age)
} yield foo
foo: scalaz.WriterT[scalaz.Id.Id,scala.collection.immutable.Vector[String],String] = WriterT((Vector(search user with name:[Joe] found id:[123], state is alive, age is 18, age older then 18 is Old Foo.),Old Foo))
foo.value
scalaz.Id.Id[String] = Old Foo
foo.written
scalaz.Id.Id[scala.collection.immutable.Vector[String]] = Vector(search user with name:[Joe] found id:[123], state is alive, age is 18, age older then 18 is Old Foo.)
可以看到,因為是monad,所以我這邊透過for comprehension來把這些動作串起來,最後產生了foo
,而他的value
就是最後處理完成的結果,而written
就是我每個過程中附加上來的log,是不是簡單好用呢?
有關效能的注意事項
當你要把一個Monoid(在我們的例子用的Monoid是Vector)放在Writer Monad裡的時候,要注意performance的考量。Learning-Scalaz的文章裡有做了個測試,若是用List的話,花的時間會是Vector的兩倍哦!
Reference
- Learning-Scalaz Writer
- Learning-Scalaz Id
沒有留言:
張貼留言