この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
先日のScala関西 Summit での実践Monix導入 でMonixに興味を持ったので、セッションで紹介されたTaskと同じくmonix-evalに含まれているCoeval について調べてみました。 (ところで上記の実践Monix導入の資料は非常にわかりやすく内容も充実した資料なのでおすすめです)
この記事ではいくつかあるCoevalの生成方法をまとめてあります。
Coevalとは
ドキュメントによると
Coeval is a data type for controlling synchronous, possibly lazy evaluation, useful for describing lazy expressions and for controlling side-effects. It is the sidekick of Task, being meant for computations that are guaranteed to execute immediately (synchronously).
Coevalは同期的に実行される(遅延評価の可能性もある)計算のためのデータ型で、遅延式の記述や副作用の制御に役立ちます。
Design Summaryには以下のような点が挙げられています
- resembles Task, but works only for immediate, synchronous evaluation
- can be a replacement for lazy val and by-name parameters
- doesn’t trigger the execution, or any effects until value or run
- allows for controlling of side-effects
- handles errors
使い方
Coevalの定義は以下のようになっています。 型パラメータAは計算結果をあらわします。
sealed abstract class Coeval[+A] extends (() => A)
以下のように任意の計算処理をラップして遅延評価される値として扱えます。 処理はCoevalのインスタンスを作った時ではなく参照するときに行われます。
val c = Coeval.eval{
println("Effect")
"Hello"
}
println(c.value)
// Effect
// Hello
生成方法
Coevalにはいくつかの生成方法が提供されています。大きく分けると次の3種類です。 生成方法に応じて処理の実行の詳細を制御することができるので、まずはこれをみていきたいと思います。
Coeval#eval
Coeval#defer
Coeval#now
以下、それぞれ説明していきます。
Coeval#eval
Coeval#eval
では 遅延評価する計算をCoevalであらわします。
#apply
, #delay
は#apply
のエイリアスです。
計算結果は以下のように #value
で取り出せます。計算はCoevalの生成時ではなく#value
の呼び出し時に実行されます
val c = Coeval.eval{
println("Effect")
"Hello"
}
println("before")
println(c.value)
println("after")
// before
// Effect
// Hello
// after
Coeval#defer
Coeval#defer
はCoevalの生成を遅延させる生成方法です。
#suspend
は #defer
のエイリアスです。
これを使うと再帰処理をスタックセーフに記述することができます。
def fib(cycles: Int, a: BigInt, b: BigInt): Coeval[BigInt] = {
if (cycles > 0)
Coeval.defer(fib(cycles - 1, b, a + b))
else
Coeval.now(b)
}
println(fib(50000, 0, 1).value())
// 17438741378727786830388554330726990163944611....
Coeval#pure
Coeval#pure
は既知の値をCoevalにリフトします。#now
は#pure
のエイリアスです。
引数で渡す値は即時評価されるので下記のように計算済みの値以外を渡すのは推奨されません。
val c = Coeval.pure {
println("Effect")
"Hello"
}
println("before execute")
println(c.value())
println("after execute")
// Effect
// before execute
// Hello
// after execute
Coeval#evalOnce
lazy val
のように遅延評価を一度だけ行うCoevalを生成します
一度評価されると結果がメモ化され、2度目以降には評価が行われません。
実装を見ると評価時にsynchronized
することでスレッドセーフになっているようです。
val memorized = Coeval.evalOnce {
println("Effect")
"Hello"
}
println("before execute")
println(memorized.value())
println(memorized.value())
println("after execute")
// before execute
// Effect
// Hello
// Hello
// after execute
まとめ
Coevalの生成方法をまとめてみました。 生成以外にも実行方法の制御など機能が豊富なので何回かに分けて整理していきたいと思います。