DB のセッションだったり、処理中の HTTP request だったり、何らかのコンテキストを引き回したいときに implicit parameter 使うことがある。
Context#x()
が Int をとって Int を返すとすると、 implicit parameter で引き回すプログラムはこんなかんじになる。
// 適当な関数 f と g の定義
def f(i: Int)(implicit context: Context): Int = context.x(i)
def g(i: Int)(implicit context: Context): Int = f(i) * 2
// 呼び出し
implicit val myContext: Context = ???
val x = g(1)
val y = g(x)
呼び出し時に毎回 Context を引数に書かなくても良いのが良い所。
implicit を使わなずに普通の引数にすると呼び出し時に毎回コンテキストを渡すことになる。
// 適当な関数 f と g の定義
def f(i: Int, context: Context): Int = context.x(i)
def g(i: Int, context: Context): Int = f(i) * 2
// 呼び出し
val x = g(1, myContext)
val y = g(x, myContext)
scalaz.Reader の場合。
// 適当な関数 f と g の定義
def f(i: Int): Reader[Context, Int] =
Reader { context => context.x(i) }
def g(i: Int): Reader[Context, Int] = f(i).map(_ * 2)
// 呼び出し
(for {
x <- doSomething(1)
y <- doSomething(x)
} yield y).run(myContext)
type alias を定義すればスッキリ見える。
type Work[A] = Reader[Context, A]
def f(i: Int): Work[Int] = Reader { context => context.x(i) }
def g(i: Int): Work[Int] = f(i).map(_ * 2)
f や g の =
の前までは一番短い。
呼び出しでは暗黙的にコンテキストを渡すことは出来ない。for式などを使うことで、複数の Reader を組み合わせることができるので、最後 run するときだけ渡せば良い。呼び出し時に context を何度も書く必要はない。
暗黙的にコンテキストを渡す場合には複数ある implicit の探索箇所のいずれかにデフォルトコンテキストを忍ばせるなどができる。run 時に明示的に渡すのはそれはそれで気持ちが良い。
他のモナドと混ぜるときには ReaderT
をつかうか、for などをネストするかになるのかな。すこしめんどい。
いままで implicit で引き回していたところを Reader を使うようにすれば、implicit は型クラスだけに使うべき派(?)になれるのかもしれない。