Memo
エフェクトはコストがかかる計算のキャッシュを可能にする。計算結果は与えられたキーにひもづけて「保存」され、同じキーに対する次の計算では以前に計算された値が返される。これらの計算をインタープリターで解釈するには、Cache
を提供しなければいけない。
import cats.Eval
import cats.implicits._
import org.atnos.eff._, memo._
import org.atnos.eff.syntax.memo._
import org.atnos.eff.syntax.eval._
import org.atnos.eff.syntax.eff._
type S = Fx.fx2[Memoized, Eval]
var i = 0
def expensive[R :_memo]: Eff[R, Int] =
memoize("key", { i += 1; 10 * 10 })
(expensive[S] >> expensive[S]).runMemo(ConcurrentHashMapCache()).runEval.run === 100
"there is only one invocation" <==> (i === 1)
> there is only one invocation <=> true
このライブラリには、Memo エフェクトをサポートするためのキャッシュ実装が 2 つある。
org.atnos.eff.ConcurrentHashMapCache
: 内部で
java.util.concurrent.ConcurrentHashMap
を使っており、そのキーは値をメモ化するために使うキーのハッシュ値となる。このキャッシュはスレッドセーフだが、制限がないので注意して使おう!
org.atnos.eff.ConcurrentWeakIdentityHashMapCache
: 内部で
ConcurrentWeakIdentityHashMap
を使っており、そのキーは値をメモ化するために使うキーの
System.identityHashCode
となる。このキャッシュはスレッドセーフであり、弱い参照を使っているので必要なときにガベージコレクトされる。
キャッシュサイズの上限、キャッシュエビクションポリシー(上限まで来たときに古いキャッシュを追い出すなど)、といった機能が欲しいなら、Caffeine
のような他のより良いキャッシュ実装を使うこともできる。Cache
インターフェースを実装すればよい。
trait Cache {
def memo[V](key: AnyRef, value: =>V): V
}