Skip to content

Instantly share code, notes, and snippets.

@ellbur
Created January 12, 2021 03:55
Show Gist options
  • Save ellbur/de2c1adee05ae21a3631886c83128b0e to your computer and use it in GitHub Desktop.
Save ellbur/de2c1adee05ae21a3631886c83128b0e to your computer and use it in GitHub Desktop.
Dirt-simple reactive signals in Scala 3
package signals
import scala.collection.mutable
trait Target[+A] {
def rely(upset: () => () => Unit): (A, Cancellable)
import TrackingUtils.tracking
def map[B](f: A => B): Target[B] = tracking { f(this.track) }
def flatMap[B](f: A => Target[B]) = tracking { f(this.track).track }
def zip[B](other: Target[B]): Target[(A, B)] = tracking { (this.track, other.track) }
def track(using tracking: Tracking): A = tracking.track(this)
}
trait Cancellable {
def cancel(): Unit
}
trait ComputedTarget[A] extends Target[A] {
private val listeners = mutable.ArrayBuffer[Listener]()
private val listened = mutable.ArrayBuffer[Cancellable]()
private class Listener(upset: () => () => Unit) {
def apply(): () => Unit = {
upset()
}
}
def rely(upset: () => () => Unit): (A, Cancellable) = {
val listener = new Listener(upset)
listeners += listener
(
compute,
new Cancellable {
def cancel(): Unit = {
listeners -= listener
if (listeners.isEmpty) {
val currentListened = listened.toSeq
listened.clear()
currentListened foreach (_.cancel())
}
}
}
)
}
protected def upset(): () => Unit = {
val currentListeners = listeners.toSeq
listeners.clear()
val next =
currentListeners map { l =>
l()
}
() => {
next foreach (_())
}
}
protected def relyOn[B](s: Target[B]): B = {
val (b, c) = s.rely(upset)
listened += c
b
}
protected def compute: A
}
class Source[A](_init: => A) extends ComputedTarget[A] {
private var it: A = _init
def update(next: A): Unit = {
it = next
upset()()
}
protected def compute: A = it
}
object TrackingUtils {
def tracking[A](f: Tracking ?=> A): Target[A] = new ComputedTarget[A] {
protected def compute = {
f(using new Tracking {
def track[A](t: Target[A]) = relyOn(t)
})
}
}
}
trait Tracking {
def track[A](t: Target[A]): A
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment