Monix Coeval の生成メソッドまとめ

Monix Coevalの生成方法をまとめてみました。
2019.10.30

この記事は公開されてから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の生成方法をまとめてみました。 生成以外にも実行方法の制御など機能が豊富なので何回かに分けて整理していきたいと思います。