Created
January 12, 2021 03:55
-
-
Save ellbur/de2c1adee05ae21a3631886c83128b0e to your computer and use it in GitHub Desktop.
Dirt-simple reactive signals in Scala 3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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