-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Introduce another version of Signals that has no uninitialized field. #11238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce another version of Signals that has no uninitialized field. #11238
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's a good version, but it would be nice if noCaller
was Signal[Nothing]
because that is the right semantics for it.
What changes are necessary to achieve this?
To do that, So if you want Anyway, this is what we need: import annotation.unchecked._
package frp:
trait Signal[+T]:
def apply()(using caller: Signal.Caller): T
object Signal:
trait Caller:
private[Signal] def computeValue(): Unit
private[Signal] def hasObserver(observer: Caller): Boolean
abstract class AbstractSignal[+T] extends Signal[T] with Caller:
private var observers: Set[Caller] = Set()
private var currentValue: T = eval(this)
protected def eval(caller: Caller): T
private[Signal] def computeValue(): Unit =
val newValue = eval(this)
val observeChange = observers.nonEmpty && newValue != currentValue
currentValue = newValue
if observeChange then
val obs = observers
observers = Set()
obs.foreach(_.computeValue())
private[Signal] def hasObserver(observer: Caller): Boolean =
observers.contains(observer)
def apply()(using caller: Caller): T =
observers += caller
assert(!caller.hasObserver(this), "cyclic signal definition")
currentValue
end AbstractSignal
def apply[T](expr: Caller ?=> T): Signal[T] =
new AbstractSignal[T]:
protected def eval(caller: Caller) = expr(using caller)
computeValue()
class Var[T](private var expr: Caller ?=> T) extends AbstractSignal[T]:
protected def eval(caller: Caller) = expr(using caller)
def update(expr: Caller ?=> T): Unit =
this.expr = expr
computeValue()
end Var
given noCaller: Caller = new Caller:
private[Signal] def computeValue(): Unit = ()
private[Signal] def hasObserver(observer: Caller): Boolean = false
end Signal
end frp This is less simple, but it is also a better design. With that, Regardless, in the context of the moocs, I believe the version where it is a |
@@ -0,0 +1,83 @@ | |||
|
|||
import annotation.unchecked._ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import annotation.unchecked._ | |
// An alternative to Signals1 that does not rely on an uninitialized variable | |
import annotation.unchecked._ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.
I agree this is an interesting alternative. |
533ed08
to
b9ad750
Compare
In this case,
Signals1.scala
was even worse than what's in lampelf/moocs, because it actually relied on the default value assigned by the= _
, i.e.,null
, in the sense that it actually read that value and compared it with!=
to the first computed value.This is the diff compared to
Signals1.scala
: