初めてのエフェクト

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つのエフェクトを宣言している。

さて、ReaderEffectWriterEffect そして 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 型のすべてのエフェクトはひとつずつ実行される。

  1. Reader エフェクトは、値 6 を、それを必要とする計算に提供する
  2. Writer エフェクトはメッセージをログ出力する
  3. Eval エフェクトは「2 の乗算」を計算する
  4. run は最終結果を取り出す


エフェクトがスタックでの宣言順と同じ順序で実行されていないことに気づいたかもしれない。エフェクトは実はどんな順序でも実行できる。ただ、どんな順序でも結果が同じになることを意味しない。例えば、Writer エフェクトの次に Either エフェクトを実行したら Either[String, (A, List[String])] が返るのに対して、Either エフェクトの次に Writer エフェクトを実行すると (Either[String, A], List[String]) が返る。

これはすべて、Scala の型推論を正しい返り値に導いてくれるいくつかの implicit 定義のおかげだ。implicits については implicits の章でさらに学べる.

このあとは、チュートリアル を読めば Eff モナドの使い方についてさらに具体的な説明が得られるし、このライブラリがサポートしている すぐ使えるエフェクト を学んでもいい。