ロギングにscalaz.WriterTをどのように使用しますか?
2 に答える
モナド変換子について
これは非常に短い紹介です。詳細については、haskellwikiまたは@jrwestによるこのすばらしいスライドをご覧ください。
モナドは構成されません。つまり、モナドA[_]
とモナドがある場合B[_]
、自動的A[B[_]]
に導出することはできません。ただし、ほとんどの場合、これは、特定のモナドに対していわゆるモナド変換子を使用することで実現できます。
BT
モナド用のモナド変換子がある場合は、任意のモナド用B
に新しいモナドを作成できます。そうです、を使って中に入れることができます。A[B[_]]
A
BT
B
A
scalazでのモナド変換子の使用
率直に言って、私はscalaz 6でモナド変換子を使用しなかったので、以下はscalaz7を想定しています。
モナド変換子MT
は2つの型パラメーターを取ります。1つはラッパー(外部)モナドで、2つ目はモナドスタックの最下部にある実際のデータ型です。注:より多くのタイプパラメーターが必要になる場合がありますが、それらはトランスフォーマーネスとは関係ありませんが、特定のモナドに固有です(ログに記録されたタイプのWriter
、またはエラータイプのValidation
)。
したがってList[Option[A]]
、単一の構成されたモナドとして扱いたいものがある場合は、が必要OptionT[List, A]
です。ある場合はOption[List[A]]
、が必要ListT[Option, A]
です。
そこに着く方法?トランスフォーマー以外の値がある場合は、通常、それをラップしMT.apply
てトランスフォーマー内の値を取得できます。変換されたフォームから通常の形式に戻すには、通常、.run
変換された値を呼び出します。
つまりval a: OptionT[List, Int] = OptionT[List, Int](List(some(1))
、とval b: List[Option[Int]] = a.run
は同じデータであり、表現だけが異なります。
Tony Morrisは、変換されたバージョンにできるだけ早く入り、それをできるだけ長く使用するのが最善であると提案しました。
注:トランスフォーマーを使用して複数のモナドを構成すると、通常のデータ型とは正反対のタイプのトランスフォーマースタックが生成されます。したがって、通常List[Option[Validation[E, A]]]
は次のようになりますtype ListOptionValidation[+E, +A] = ValidationT[({type l[+a] = OptionT[List, a]})#l, E, A]
更新:scalaz 7.0.0-M2の時点でValidation
は、(正しく)モナドではないため、ValidationT
存在しません。EitherT
代わりに使用してください。
ロギングにWriterTを使用する
必要に応じて、WriterT
特定の外部モナドなしで使用するか(この場合、バックグラウンドでId
何もしないモナドを使用します)、ログをモナド内に配置するか、モナドをログ内に配置することができます。
最初のケース、単純なロギング
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)
listMonoid
インスタンスも提供するため、インスタンスをインポートしSemigroup[List]
ます。WriterT
ログ値を組み合わせることができるようにするには、ログタイプがセミグループである必要があるため、これが必要です。
2番目のケース、モナド内でのロギング
ここでは、Option
簡単にするためにモナドを選択しました。
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))
このアプローチでは、ロギングはOption
モナド内にあるため、バインドされたオプションのいずれかがである場合、ログなしNone
で結果が得られます。None
注:x.point[Option]
実質的にはと同じですがSome(x)
、コードをより一般化するのに役立つ場合があります。今のところ、致命的ではありません。
3番目のオプション、モナドの外でログを記録する
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)
ここでは、モナドを内OptionT
に配置するために使用します。計算の1つは、この場合でもログが保持されることを示すことです。Option
Writer
None
最後に
これらの例List[String]
では、ログタイプとして使用されています。ただし、使用するString
ことが最善の方法とは言えず、ロギングフレームワークによって強制されるいくつかの規則があります。たとえば、カスタムログADTを定義し、出力する必要がある場合は、できるだけ遅く文字列に変換することをお勧めします。このようにして、ログのADTをシリアル化し、後でプログラムで簡単に分析できます(文字列を解析する代わりに)。
WriterT
ロギングを容易にするために使用できる便利なメソッドが多数あり、ソースを確認してください。たとえば、を指定するw: WriterT[...]
と、を使用して新しいログエントリを追加したり、などw :++> List("other event")
を使用して現在保持されている値を使用してログを記録したりできます。w :++>> ((v) => List("the result is " + v))
例には、多くの明示的で長いコード(タイプ、呼び出し)があります。いつものように、これらは明確にするためのものであり、一般的な型と操作を抽出することにより、コードでそれらをリファクタリングします。
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")