メンバー implicits

型注釈を省略できるようにするために、 Eff モナドを扱うときの型推論は少しトリッキーになっている。助けとなる TIPS がいくつかある。

型パラメータが複数あるエフェクトを実行する

ReaderWriter のように、エフェクトによっては2つの型パラメータを使う。エフェクトスタックでこれらのエフェクトを追加したければ、次の scalac オプションを有効にしておく必要がある。

scalacOptions += "-Ypartial-unification"

context bound と型エイリアスを使う

エフェクトを作るときはいつも、MemberIn 型クラスと正しいエフェクトを含んだスタックが「必要」となる。

import org.atnos.eff._
import org.atnos.eff.all._

type StateInt[A] = State[Int, A]
type WriterString[A] = Writer[String, A]

// State エフェクトを作成するため
def putAndTell[R](i: Int)(implicit s: StateInt |= R, w: WriterString |= R): Eff[R, Int] =
  for {
    // 型注釈は必要ない!!
    _ <- put(i)
    _ <- tell("stored " + i)
  } yield i

putAndTell の定義をより簡潔にするために context bound を使うこともできる。

import org.atnos.eff.all._

type _stateInt[R] = State[Int, *] |= R
type _writerString[R] = Writer[String, *] |= R

def putAndTell[R :_stateInt :_writerString](i: Int): Eff[R, Int] =
  for {
    _ <- put(i)
    _ <- tell("stored " + i)
  } yield i

Member 型クラスを知る

エフェクトスタックを構成するエフェクトを宣言するには3つの異なる方法、型クラスがある。

型クラス エイリアス 意味 利用場面
MemberIn[M, R] M |= R MR の一部 R に含まれるM エフェクトを作るとき
MemberInOut[M, R] M /= R MR の一部であり、そこから取り出せる エフェクト MInterpreter.scala を参照)をインターセプトし、同じスタックにとどまりながら変換するとき。例えば Error エフェクトを handleError するとき。
Member[M, R] M <= R MR の一部であり、そこから取り出せて、 その結果生じるスタックは m.Out となる 特別な値または他のエフェクトについてエフェクトを解釈し、そのスタックからエフェクトを取り除くとき

Member インスタンスを圧縮する

複数の関数が同じエフェクトリストを必要するときは、関数のシグネチャがくどくなることがある。

def foo1[R :_foo :_bar :_baz]: Eff[R, Int]
def foo2[R :_foo :_bar :_baz]: Eff[R, Int]
def foo3[R :_foo :_bar :_baz]: Eff[R, Int]

_effects 型を定義することで、これらを「圧縮」できる。

import org.atnos.eff.Members.{&:, &&:}

trait Foo[A]
trait Bar[A]
trait Baz[A]
trait Boo[A]

type _foo[R] = Foo |= R
type _bar[R] = Bar |= R
type _baz[R] = Baz |= R

// 最後の &&: の代わりに、_foo[R] &: _bar[R] &: _baz[R] &: NoMember と書くこともできる
type _effects[R] = _foo[R] &: _bar[R] &&: _baz[R]

def getFoo[R :_foo :_bar]: Eff[R, Int] = Eff.pure(1)
def getBar[R :_bar]: Eff[R, Int] = Eff.pure(1)
def getBaz[R :_baz]: Eff[R, Int] = Eff.pure(1)

object t {

  import org.atnos.eff.Members.extractMember

  def foo[R :_effects](i: Int): Eff[R, Int] =
    getFoo[R] >>
    getBar[R] >>
    getBaz[R]

}

// 具体的なスタックで foo を呼び出す
type S = Fx.fx3[Foo, Bar, Baz]
type U = Fx.fx4[Foo, Bar, Baz, Boo]

t.foo[S](1)
t.foo[U](1)

Members オブジェクト内の implicit 定義 extractMember は、必要時に Member インスタンスを「展開」を引き受ける。「圧縮」された implicits を必要とするメソッドを呼び出すときに、この implicit がスコープ内に存在してはならない。 さもないと、間違った implicit 探索を引き起こしてしまう。