Skip to content

Commit 533ed08

Browse files
committed
Introduce another version of Signals that has no uninitialized field.
1 parent 3568ecd commit 533ed08

File tree

2 files changed

+86
-0
lines changed

2 files changed

+86
-0
lines changed

tests/run/Signals2.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
0
2+
10
3+
30

tests/run/Signals2.scala

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
2+
import annotation.unchecked._
3+
package frp:
4+
5+
trait Signal[+T]:
6+
def apply()(using caller: Signal.Caller): T
7+
8+
object Signal:
9+
10+
abstract class AbstractSignal[+T] extends Signal[T]:
11+
private var observers: Set[Caller] = Set()
12+
private var currentValue: T = eval(this)
13+
14+
protected def eval(caller: Caller): T
15+
16+
protected def computeValue(): Unit =
17+
val newValue = eval(this)
18+
val observeChange = observers.nonEmpty && newValue != currentValue
19+
currentValue = newValue
20+
if observeChange then
21+
val obs = observers
22+
observers = Set()
23+
obs.foreach(_.computeValue())
24+
25+
def apply()(using caller: Caller): T =
26+
observers += caller
27+
assert(!caller.observers.contains(this), "cyclic signal definition")
28+
currentValue
29+
end AbstractSignal
30+
31+
def apply[T](expr: Caller ?=> T): Signal[T] =
32+
new AbstractSignal[T]:
33+
protected def eval(caller: Caller) = expr(using caller)
34+
computeValue()
35+
36+
class Var[T](private var expr: Caller ?=> T) extends AbstractSignal[T]:
37+
protected def eval(caller: Caller) = expr(using caller)
38+
39+
def update(expr: Caller ?=> T): Unit =
40+
this.expr = expr
41+
computeValue()
42+
end Var
43+
44+
opaque type Caller = AbstractSignal[?]
45+
given noCaller: Caller = new AbstractSignal[Unit]:
46+
protected def eval(caller: Caller) = ()
47+
override def computeValue() = ()
48+
49+
end Signal
50+
end frp
51+
52+
import frp._
53+
class BankAccount:
54+
def balance: Signal[Int] = myBalance
55+
56+
private val myBalance: Signal.Var[Int] = Signal.Var(0)
57+
58+
def deposit(amount: Int): Unit =
59+
if amount > 0 then
60+
val b = myBalance()
61+
myBalance() = b + amount
62+
63+
def withdraw(amount: Int): Int =
64+
if 0 < amount && amount <= balance() then
65+
val b = myBalance()
66+
myBalance() = b - amount
67+
myBalance()
68+
else throw new AssertionError("insufficient funds")
69+
end BankAccount
70+
71+
@main def Test() =
72+
def consolidated(accts: List[BankAccount]): Signal[Int] =
73+
Signal(accts.map(_.balance()).sum)
74+
75+
val a = BankAccount()
76+
val b = BankAccount()
77+
val c = consolidated(List(a, b))
78+
println(c())
79+
a.deposit(10)
80+
println(c())
81+
b.deposit(20)
82+
println(c())
83+
end Test

0 commit comments

Comments
 (0)