0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ScalaでComputedな値(算出プロパティ)を実装する

Last updated at Posted at 2022-05-19

jsフレームワークのVueには算出プロパティという便利機能がありまして、
こんな感じの機能をScalaでも使いたかったので実装してみました。

Scalaは雰囲気で使ってるのでよくわかりません。

Vueの算出プロパティって一般的な計算機用語でなんていうんですかね?
スマートゲッターとかメモ化ゲッターじゃなさそう?

以下本体。

Computed.scala
import scala.collection.mutable

class Computed[T](private var func: Option[Computed[_]] => T) {
  private var cache: Option[T] = None
  private var value: Option[T] = None
  private val parents = new mutable.WeakHashMap[Computed[_], Boolean]
  private var listeners = Seq.empty[Unit => Unit]

  def get(implicit computed: Option[Computed[_]]): T = {
    computed.foreach(parents(_) = true)
    if (needsUpdate) {
      cache = Some(func(Some(this)))
    }
    cache.get
  }

  def :=(func: Option[Computed[_]] => T): Unit = {
    if (func != this.func) {
      value = None
      this.func = func
      modified()
    }
  }

  def ::=(value: T): Unit = {
    val optional = Option(value)
    if (optional != this.value) {
      this.value = optional
      :=(_ => value)
    }
  }

  def needsUpdate: Boolean = cache.isEmpty

  def modified(): Seq[Unit => Unit] = {
    cache = None
    val list = mutable.ListBuffer.empty[Unit => Unit]
    list ++= listeners
    parents.keys.foreach(list ++= _.modified())

    parents.clear()
    list
  }

  def addOnChangeListener(listener: Unit => Unit): Unit = {
    listeners :+= listener
    listeners = listeners.distinct
  }
  def clearListener(): Unit = {
    listeners = Seq()
  }
}

object Computed {
  def apply[T](func: Option[Computed[_]] => T): Computed[T] = new Computed(func)
  def value[T](value: T): Computed[T] = {
    val computed = new Computed(_ => value)
    computed.value = Option(value)
    computed
  }
}

以下便利ImplicitConversion。

Extensions.scala
import scala.language.implicitConversions

object Extensions {
  implicit def computedToValue[R](computed: Computed[R])(implicit parent: Option[Computed[_]] = None): R = computed.get
  implicit def $v[T](value: T): Computed[T] = Computed.value(value)
}

以下使い方。

import test.{Computed => $}
import test.Extensions._

......

val value = $v("最初の値")
var count = 0
var deepCount = 0
val computed = $ { implicit c =>
  count += 1
  value + "です"
}
val deepComputed = $ { implicit c =>
  deepCount += 1
  computed + "だよ"
}

println(count)              // 0
println(computed.get(None)) // 最初の値です
println(count)              // 1
println(computed.get(None)) // 最初の値です
println(count)              // 1

value ::= "次の値"

println(deepCount)              // 0
println(deepComputed.get(None)) // 次の値ですだよ
println(deepCount)              // 1
println(deepComputed.get(None)) // 次の値ですだよ
println(deepCount)              // 1

println(count)              // 2
println(computed.get(None)) // 次の値です
println(count)              // 2

最初と更新が入って必要なときだけ計算し、それ以外はキャッシュを返してそうですね。
Scala + libGDXでスマホゲームを作っていて、画面レイアウトを計算するのに便利そうだったので作りました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?