レシピ集

Eff を使った特定の問題の解き方の例のコレクション。

部分的な解釈

プログラムの異なる場面で異なるエフェクトを使いたいのはよくあることだ。いくつかのエフェクト、たとえばエラー処理やロギングは、プログラムの全体に広がる。しかし、さらなるエフェクト、たとえば状態を、全体の一部として組み込みたいことがある。

以下の例は、これをどうできるかを示している。

import cats._, data._
import cats.implicits._
import org.atnos.eff._
import org.atnos.eff.all._
import org.atnos.eff.syntax.all._

// 使う予定のエフェクトのための型定義
type EitherString[A] = Either[String, A]
type WriterString[A] = Writer[String, A]
type StateInt[A]     = State[Int, A]

type _err[R] = EitherString |= R
type _log[R] = WriterString |= R
type _count[R] = StateInt |= R


/**
 * モジュール1:いくつかの便利メソッド
 */

// 副作用を n 回繰り返す
def repeatM[M[_] : Monad](n: Int, computation: M[Unit]): M[Unit] =
  if (n <= 0) computation
  else computation >> repeatM(n - 1, computation)

// 条件をチェックし、条件が false なら計算を停止してメッセージを出す
def assert[R :_err](condition: Boolean, msg: String): Eff[R, Unit] =
  if (!condition) left(msg) else right(())

// カウンター値を増加し、新しい値をログ出力する
def incrementCounter[R :_log :_count]: Eff[R, Unit] = for {
  c <- get
  c2 = c + 1
  _ <- tell(s"counter == $c2")
  _ <- put(c2)
} yield ()


/**
 * モジュール2:「ビジネス」ロジック
 */

// 値を n 回増加させる(n は正の必要がある)
def incrementNTimes[R :_err :_log](start: Int, times: Int): Eff[R, Int] = for {
  // この呼出はスタック R を使う
  _ <- assert(times >= 0, s"$times is negative")

  // 次の呼出はスタック R と、すぐに解釈される追加の StateInt エフェクトを使う
  // その他のエフェクトは残る
  result <- repeatM(times, incrementCounter[Fx.prepend[StateInt, R]]).execState(start)
} yield result


/**
 * トップレベルの呼出
 */

type Stack = Fx.fx2[EitherString, WriterString]

incrementNTimes[Stack](3, 2).runWriter.runEither.run

> Right((6,List(counter == 4, counter == 5, counter == 6)))