型注釈を省略できるようにするために、 Eff モナドを扱うときの型推論は少しトリッキーになっている。助けとなる TIPS がいくつかある。
Reader
や
Writer
のように、エフェクトによっては2つの型パラメータを使う。エフェクトスタックでこれらのエフェクトを追加したければ、次の
scalac
オプションを有効にしておく必要がある。
scalacOptions += "-Ypartial-unification"
エフェクトを作るときはいつも、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
|
M は
R の一部
|
R に含まれるM
エフェクトを作るとき
|
MemberInOut[M, R]
|
M /= R
|
M は
R
の一部であり、そこから取り出せる
|
エフェクト M (
Interpreter.scala
を参照)をインターセプトし、同じスタックにとどまりながら変換するとき。例えば
Error エフェクトを
handleError するとき。
|
Member[M, R]
|
M <= R
|
M が
R
の一部であり、そこから取り出せて、 その結果生じるスタックは
m.Out となる
|
特別な値または他のエフェクトについてエフェクトを解釈し、そのスタックからエフェクトを取り除くとき |
複数の関数が同じエフェクトリストを必要するときは、関数のシグネチャがくどくなることがある。
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 探索を引き起こしてしまう。