新しいエフェクトはとても簡単にこのライブラリに追加できる。Effect 型を新しい「オプショナル型」用に作ってみよう。
必要なものは、
ベースとなる型。Maybe
データ型とする。2つのケース
Just
と
Nothing
がある。
A
型の値を
Eff[R, A]
に送り込むメソッド。
インタープリター
import cats._, implicits._
import org.atnos.eff._
import all._
import org.atnos.eff.interpret._
sealed trait Maybe[A]
case class Just[A](a: A) extends Maybe[A]
case class Nothing[A]() extends Maybe[A]
object MaybeEffect {
type _maybe[R] = Maybe |= R
def just[R :_maybe, A](a: A): Eff[R, A] =
send[Maybe, R, A](Just(a))
def nothing[R :_maybe, A]: Eff[R, A] =
send[Maybe, R, A](Nothing())
def runMaybe[R, U, A, B](effect: Eff[R, A])(implicit m: Member.Aux[Maybe, R, U]): Eff[U, Option[A]] =
recurse(effect)(new Recurser[Maybe, U, A, Option[A]] {
def onPure(a: A) = Some(a)
def onEffect[X](m: Maybe[X]): Either[X, Eff[U, Option[A]]] =
m match {
case Just(x) => Left(x)
case Nothing() => Right(Eff.pure(None))
}
def onApplicative[X, T[_]: Traverse](ms: T[Maybe[X]]): Either[T[X], Maybe[T[X]]] =
Right(ms.sequence)
})
implicit val applicativeMaybe: Applicative[Maybe] = new Applicative[Maybe] {
def pure[A](a: A): Maybe[A] = Just(a)
def ap[A, B](ff: Maybe[A => B])(fa: Maybe[A]): Maybe[B] =
(fa, ff) match {
case (Just(a), Just(f)) => Just(f(a))
case _ => Nothing()
}
}
}
上のコードでは、
just
と
nothing
メソッドは、値を大きなエフェクト
Eff[R, A]
に送り込むために、
Eff.send
を使っている。
runMaybe
は、Maybe
値を Option
値に翻訳するために
interpret.recurse
と
Recurser
を使うことで
Maybe
エフェクトを実行している。
エフェクトを作るときは、そのエフェクトで起こり得ることの区別を表現するために sealed trait と case class を定義できる。たとえばデータベースとやりとりするときはこんな風に作るだろう。
trait DatabaseEffect {
case class Record(fields: List[String])
sealed trait Db[A]
case class Get[A](id: Int) extends Db[Record]
case class Update[A](id: Int, record: Record) extends Db[Record]
}
実際は Db
型を
DatabaseEffect
trait
の外側で作ることをおすすめする。Member
implicit が解決されるときに、Db
エフェクト型をどのようにインポートしているか(オブジェクトから継承しているか否か)によって、コンパイラーのクラッシュを経験するかもしれない
:-(。
通常、与えられたエフェクトを「解釈
」するということは、型
M[X]
の値に対してすべきことが分かっているということを意味する。 ここで
M
はエフェクトだ。もしインタープリターがエフェクトを「実行
」できるのなら、つまりログを出力したり(Writer
)、非同期に計算したり(Future
)、値をチェックしたり(Either
)できるなら、X
を取り出して、そして次のエフェクトを取得して同じ用に解釈できるように継続
を呼び出すことができる。
org.atnos.eff.interpret
オブジェクトはインタープリターを書くためのいくつかの trait
と関数を提供している。この例では
Recurser
を使う。これは、Maybe[X]
から値 X
を
「取り出す」、もしくは諦めて
Eff.pure(None)
を実行する。
runMaybe
メソッドは
Member.Aux[Maybe, R, U]
型の implicit
パラメーターを必要とする。この型は次のように解釈すべきだ。
Maybe
はエフェクトスタック
R
のメンバーでなければならず、R
から取り除くにはエフェクトスタック
U
となるべきだ。
これでこのエフェクトを計算で使える。
import org.atnos.eff._
import org.atnos.eff.eff._
import MaybeEffect._
val action: Eff[Fx.fx1[Maybe], Int] =
for {
a <- just(2)
b <- just(3)
} yield a + b
run(runMaybe(action))
> Some(5)