Call Stack (Context)
Ability to query the "context" from within a signal using when. The context contains a stack of values pushed onto the stack during compute of a signal. For example, invoking a signal b that is a dependency for a will cause b to be pushed to the context stack when computing a.
const c = on()
const b = on()
const a = on(() => {
// touch another signal
c()
if (when(b)) {
return true
}
return false
})
The when function returns a boolean; true if the value is in the stack, and false if it's not. Any value queried by when is auto-tracked.
Context or "call stack" could be better named. The idea is that when gives you access to state of the stack trace. This isn't something you can necessarily do in a typical function scope unless the function is defined within a closure of another. Instead, this stack is an alternative way of passing hidden state other than parameters for added "context awareness". This may be an anti-pattern, but it makes for some convenient ways to emulate other kinds of signal behaviors.
Disable Auto-Tracking with off
Instead of a .peek() method, we can use off(signal)(params) to wrap a signal to disable auto-tracking. This change fits nicely with when where we may want to "peek" into some context value: when(off(b)).
Stateless by Default
All signals with a compute function are stateless. State will be solely managed by own. If a compute function doesn't set a value to own (e.g. own(value)), then no state is kept. The function can return a different value from the internal state that is kept by own. This allows for private state:
// Return a boolean when setting the signal state, but return the state when computing the signal
const s = on((v) => {
return v != null ? own(v) % 2 === 0 : v
})
s(2) // returns true
s() // returns 2
s(1) // returns false
s() // returns 1
This added flexibility gives the user finer grain control over state management for all kinds of purposes: caching for performance, deriving a computed state from memory. The user can make more decisions on how they want to manage memory vs compute resources.
Because own takes a default value or initialization value as it's argument, how then do you update the value of own? The answer is that the state that own returns is not mutable, but it's properties may be. Use an mutable object and mutate in place for mutable internal state. An alternative to this idea is to make the argument a set state (but only if it's !==):
const s = on((v) => {
return own(own() ?? v ?? initialValue)
}
This is not pretty, but doable. Alternatively, a function could be passed:
// Function always returns its state
own(v => v ?? initialValue)
This function will always update the internal state. This is similar to React's setState function, so this may be a plus because of familiarity.
Note on type inferrence
One challenge to this design may be with proper type checking on own considering that it might have a different type from the signal. Perhaps the use of this could solve instead of own:
// Invent some utility type called Self
const s = on<T>((this: Self<I>, internal: I = this()) => ...)
// Or make the internal state function be an untracked signal
const s = on<T>((this: Signal<I>, internal: I = this()) => ...)
Call Stack (Context)
Ability to query the "context" from within a signal using
when. The context contains a stack of values pushed onto the stack during compute of a signal. For example, invoking a signalbthat is a dependency forawill causebto be pushed to the context stack when computinga.The
whenfunction returns a boolean;trueif the value is in the stack, andfalseif it's not. Any value queried bywhenis auto-tracked.Context or "call stack" could be better named. The idea is that
whengives you access to state of the stack trace. This isn't something you can necessarily do in a typical function scope unless the function is defined within a closure of another. Instead, this stack is an alternative way of passing hidden state other than parameters for added "context awareness". This may be an anti-pattern, but it makes for some convenient ways to emulate other kinds of signal behaviors.Disable Auto-Tracking with
offInstead of a
.peek()method, we can useoff(signal)(params)to wrap a signal to disable auto-tracking. This change fits nicely withwhenwhere we may want to "peek" into some context value:when(off(b)).Stateless by Default
All signals with a compute function are stateless. State will be solely managed by
own. If a compute function doesn't set a value toown(e.g.own(value)), then no state is kept. The function can return a different value from the internal state that is kept byown. This allows for private state:This added flexibility gives the user finer grain control over state management for all kinds of purposes: caching for performance, deriving a computed state from memory. The user can make more decisions on how they want to manage memory vs compute resources.
Because
owntakes a default value or initialization value as it's argument, how then do you update the value ofown? The answer is that the state thatownreturns is not mutable, but it's properties may be. Use an mutable object and mutate in place for mutable internal state. An alternative to this idea is to make the argument a set state (but only if it's!==):This is not pretty, but doable. Alternatively, a function could be passed:
This function will always update the internal state. This is similar to React's
setStatefunction, so this may be a plus because of familiarity.Note on type inferrence
One challenge to this design may be with proper type checking on
ownconsidering that it might have a different type from the signal. Perhaps the use ofthiscould solve instead ofown: