Extensible effects は、モナドトランスフォーマーに代わるもののひとつで、作用のある計算を関数的な方法で行うためのものだ。このライブラリは Oleg Kiselyov の論文 で述べられたように、「エフェクトスタック」 を作るために "free-er" モナドと extensible effect を使っている。
このアプローチには多くの利点がある。
エフェクトを「必要とする」ものとしてアクションを宣言でき、事前にどんなエフェクトが必要か詳細を決めておく必要はない。
エフェクトのハンドラーはモジュラーであり、必要に応じて(実行時でさえ)他の実装と入れ替えられる。
内部実装は高性能で、スタックセーフである。
既存のモナディックなデータ型をこのライブラリと連携できる。
エフェクトスタックを変更したり結合したりできる。
ここまではたぶん抽象的すぎるので、これらが一体何を意味するのかより詳細に見ていこう。
まず、インストールページで示されたセットアップをすべて完了しておくこと。
モナディックな作用を
Eff[R, A]
の値としてモデル化する。ここで
R
はエフェクトの集合を、A
は計算によって返る値を意味する。この計算はおそらく、評価されたときに何らかのエフェクト(副作用)を引き起こす。
エフェクト
R
は、「エフェクトコンストラクター」の型レベルリストとしてモデル化される。例えば、
import cats._, data._
import org.atnos.eff._
type ReaderInt[A] = Reader[Int, A]
type WriterString[A] = Writer[String, A]
type Stack = Fx.fx3[WriterString, ReaderInt, Eval]
このエフェクトスタック
Stack
は3つのエフェクトを宣言している。
ReaderInt
エフェクトは何らかの
Int
型の設定値にアクセスする
WriterString
エフェクトは文字列のメッセージをログ出力する
Eval
エフェクトは要求されたときにだけ値を計算する(Scala の lazy
値に少し似ている)
さて、ReaderEffect
、WriterEffect
そして
EvalEffect
によって与えられたプリミティブな操作を使うことで、3つのエフェクトのあるプログラムを書くことができる。
import org.atnos.eff.all._
import org.atnos.eff.syntax.all._
// ReaderInt と WriterString エフェクトが R の「メンバー」であると示している便利な型エイリアス。
// ここで R はさらにエフェクトを持ちうることを示している。
type _readerInt[R] = ReaderInt |= R
type _writerString[R] = WriterString |= R
def program[R :_readerInt :_writerString :_eval]: Eff[R, Int] = for {
// 設定を取得
n <- ask[R, Int]
// 現在の設定値をログ
_ <- tell("必要な乗数は " + n)
// 2 の n 乗を計算
a <- delay(math.pow(2, n.toDouble).toInt)
// 結果をログ
_ <- tell("結果は " + a)
} yield a
// すべての「インタープリター」で操作を実行。
// 各インタープリターは1つのエフェクトを実行する。
program[Stack].runReader(6).runWriter.runEval.run
> (64,List(必要な乗数は 6, 結果は 64))
ご覧のとおり、Stack
型のすべてのエフェクトはひとつずつ実行される。
Reader
エフェクトは、値
6
を、それを必要とする計算に提供する
Writer
エフェクトはメッセージをログ出力する
Eval
エフェクトは「2
の乗算」を計算する
run
は最終結果を取り出す
エフェクトがスタックでの宣言順と同じ順序で実行されていないことに気づいたかもしれない。エフェクトは実はどんな順序でも実行できる。ただ、どんな順序でも結果が同じになることを意味しない。例えば、Writer
エフェクトの次に
Either
エフェクトを実行したら
Either[String, (A, List[String])]
が返るのに対して、Either
エフェクトの次に
Writer
エフェクトを実行すると
(Either[String, A], List[String])
が返る。
これはすべて、Scala の型推論を正しい返り値に導いてくれるいくつかの implicit 定義のおかげだ。implicits については implicits の章でさらに学べる.
このあとは、チュートリアル を読めば Eff モナドの使い方についてさらに具体的な説明が得られるし、このライブラリがサポートしている すぐ使えるエフェクト を学んでもいい。