Skip to content

Commit 081fbc6

Browse files
authored
Merge pull request #11238 from dotty-staging/no-uninitialized-fields-in-signal
Introduce another version of Signals that has no uninitialized field.
2 parents d48849f + b9ad750 commit 081fbc6

File tree

2 files changed

+87
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)