Notes on let-signal evaluation
This commit is contained in:
parent
182307a81f
commit
c255e19c42
2 changed files with 53 additions and 89 deletions
|
|
@ -1,86 +0,0 @@
|
||||||
|
|
||||||
```javascript
|
|
||||||
const Thing = (initState) => ({
|
|
||||||
state: initState,
|
|
||||||
subscribers: [],
|
|
||||||
set(transform) {
|
|
||||||
const state = transform(this.state);
|
|
||||||
this.state = state;
|
|
||||||
this.subscribers.forEach(f => {
|
|
||||||
f(state);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
get() {
|
|
||||||
return this.state;
|
|
||||||
},
|
|
||||||
subscribe(f) {
|
|
||||||
this.subscribers.push(f);
|
|
||||||
},
|
|
||||||
map(transform) {
|
|
||||||
const Y = Thing(this.state);
|
|
||||||
this.subscribe(x => {
|
|
||||||
Y.set(() => transform(x));
|
|
||||||
});
|
|
||||||
|
|
||||||
return Y;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function pair(X, Y) {
|
|
||||||
const Z = Thing([X.get(), Y.get()]);
|
|
||||||
X.subscribe(x => {
|
|
||||||
Z.set(() => [x, Y.get()]);
|
|
||||||
});
|
|
||||||
Y.subscribe(y => {
|
|
||||||
Z.set(() => [X.get(), y]);
|
|
||||||
});
|
|
||||||
return Z;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comonad lift
|
|
||||||
// Signal(a), (Signal(a) -> b) -> Signal(b)
|
|
||||||
|
|
||||||
// X: Signal(A), f: Signal(A) -> B
|
|
||||||
// Y: Signal(B)
|
|
||||||
// function extend(X, f) {
|
|
||||||
// const y0 = f(X);
|
|
||||||
// const Y = Thing(y0);
|
|
||||||
// X.subscribe(x => {
|
|
||||||
// Y.set(f(???)); I need to somehow feed it a new signal...
|
|
||||||
// // TODO: Ofcourse I can feed it `X` again, but that feels wrong... I thought
|
|
||||||
// });
|
|
||||||
// return Y;
|
|
||||||
// }
|
|
||||||
|
|
||||||
const count = Thing(0);
|
|
||||||
console.log("COUNT EXISTS");
|
|
||||||
|
|
||||||
console.log(count.get())
|
|
||||||
|
|
||||||
count.subscribe(x => {
|
|
||||||
console.log("count is now", x);
|
|
||||||
});
|
|
||||||
|
|
||||||
count.set(() => 1)
|
|
||||||
count.set(() => 2)
|
|
||||||
|
|
||||||
const double = count.map(x => 2*x);
|
|
||||||
console.log("DOUBLE EXISTS");
|
|
||||||
|
|
||||||
|
|
||||||
double.subscribe(x => {
|
|
||||||
console.log("double is now", x);
|
|
||||||
});
|
|
||||||
|
|
||||||
count.set(() => 3);
|
|
||||||
count.set(() => 9);
|
|
||||||
|
|
||||||
const WTF = pair(count, double)
|
|
||||||
console.log("-> WTF EXISTS");
|
|
||||||
|
|
||||||
WTF.subscribe(([x, y]) => {
|
|
||||||
console.log("WTF is now ", [x, y]);
|
|
||||||
});
|
|
||||||
|
|
||||||
count.set(() => 13);
|
|
||||||
```
|
|
||||||
|
|
@ -472,9 +472,7 @@ let-signal {
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# Parametrise Signals
|
||||||
// === parametrised signals ===
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
// like a top-level function of type (A, B, C) -> Signal(D)
|
// like a top-level function of type (A, B, C) -> Signal(D)
|
||||||
|
|
@ -483,3 +481,55 @@ fn-signal Foo(x1, x2, x3) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Implementation
|
||||||
|
|
||||||
|
## Signal Env/Frame/Binding
|
||||||
|
|
||||||
|
```
|
||||||
|
type SignalFrame = {
|
||||||
|
pattern: ProductPattern,
|
||||||
|
expr: Signal<Value>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is almost like a signal-env. Seems useful.
|
||||||
|
|
||||||
|
## Let-Signal binding
|
||||||
|
```
|
||||||
|
let-signal {
|
||||||
|
x := sig-expr-0,
|
||||||
|
y := sig-expr-1
|
||||||
|
. f(x, y)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
What happens during the evaluation of the above signal-expression?
|
||||||
|
|
||||||
|
1. evaluate `(sig-expr-0, sig-expr-1)` to `(sig0, sig1)` and construct a signal-env
|
||||||
|
`[ x := sig0, y := sig1 ]`
|
||||||
|
2. evaluate `initVal := f(x, y) in env [ x := sig0.read(), y := sig1.read() ]`
|
||||||
|
3. construct new signal `Z := signal(initVal)`
|
||||||
|
4. make `Z` depend on `sig0` and `sig1`.
|
||||||
|
When one of them changes, push new value on `Z` that's the result of evaluation of
|
||||||
|
`f(x, y) in env [ x := sig0.read(), y := sig1.read() ]`
|
||||||
|
|
||||||
|
Note how `Z` is a signal together with a special closure:
|
||||||
|
- body of the closure is `f(x, y)`
|
||||||
|
- the captured signal-env of the closure is `[ x := sig0, y := sig1 ]`
|
||||||
|
- `Z` depends on `(sig0, sig1)`
|
||||||
|
|
||||||
|
|
||||||
|
TODO: Maybe it would be better to have something like signal-values?
|
||||||
|
These can either be plain constants,
|
||||||
|
or something more complex that has dependencies...
|
||||||
|
Right now everything is forced to be `Signal<Value>`.
|
||||||
|
|
||||||
|
```
|
||||||
|
type Signal =
|
||||||
|
| Constant(Value)
|
||||||
|
| Closure(... ? ...)
|
||||||
|
| NamedSignal(SignalName) // ???
|
||||||
|
```
|
||||||
|
|
||||||
|
But... if we allow recompilation at runtime of signals, a constant signal may become something more complex with dependencies.
|
||||||
|
That's why you always have to track dependencies - even when the original value ain't changing (atleast with the current compiled code)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue