Consider an example like this, in TypeScript:
const state = signal({
foods: ["pizza", "beer", "cake", "more pizza"],
numFoods: wire($ => state.foods($).length),
});
There's a problem with this: inference for the type of numFoods fails, because it is dependent on the type of foods, which hasn't been inferred yet. TypeScript doesn't seem to allow cycles between inferred members. (Even though this is not technically a cycle if you consider the individual property types, TypeScript appears to try to resolve the types via the inferred type of the object rather than it's properties, resulting in a cycle... or something?)
I could manually type out an interface for the signal call, but that's a bother - explicitly passing the types doesn't work, because the type of state would still be inferred:
const state = signal<{ foods: string[], numFoods: number }>({
foods: ["pizza", "beer", "cake", "more pizza"],
numFoods: wire($ => state.foods($).length),
});
I could manually type out the entire return-type - that works, but it's a real bother:
const state: { foods: Signal<string[]>, numFoods: Signal<number> } = signal({
foods: ["pizza", "beer", "cake", "more pizza"],
numFoods: wire($ => state.foods($).length),
});
I could break them apart like this:
const state = signal({
foods: ["pizza", "beer", "cake", "more pizza"],
});
const computed = signal({
numFoods: wire($ => state.foods($).length),
});
That works, but two collections isn't what I wanted.
I could wrap them in a function and tease out the inference that way:
const state = (() => {
const state = signal({
foods: ["pizza", "beer", "cake", "more pizza"],
});
const computed = signal({
numFoods: wire($ => state.foods($).length),
});
return { ...state, ...computed };
})();
That also works, but, yuck...
I'm having two thoughts here.
💭 Having to create multiple, unrelated states at once might not be a good idea. I've pointed this out before, and I know you don't agree, because you're attached to the idea of object properties turning into debug-friendly names. I'm still not convinced of that, because the names will get mangled, and this won't save you when things fail in production - I'd prefer to find a more reliable approach to debugging.
💭 Alternatively, maybe the recommended idiom is to separate state from computed state, like in the two last examples - maybe you can argue that separating mutable from computed state is "a good thing".
If so, maybe we could have a separate helper, similar to signal, for computed state only - this would just save you the repeated wire calls, when creating your derived states, but maybe it provides a more explainable concept?
const state = signal({
foods: ["pizza", "beer", "cake", "more pizza"],
});
const computed = computedSignal({
numFoods: $ => state.foods($).length,
});
I'm not particularly fond of letting implementation issues drive design - but in this case, I suppose you could argue it's good to separate mutable state from computed state? 🤔
(This issue is partially me thinking ahead to documentation/tutorials - it would be great if we didn't need to explain problems like this, and even better if users didn't have to run into them and look for a solution and explanation in the first place...)
Consider an example like this, in TypeScript:
There's a problem with this: inference for the type of
numFoodsfails, because it is dependent on the type offoods, which hasn't been inferred yet. TypeScript doesn't seem to allow cycles between inferred members. (Even though this is not technically a cycle if you consider the individual property types, TypeScript appears to try to resolve the types via the inferred type of the object rather than it's properties, resulting in a cycle... or something?)I could manually type out an interface for the
signalcall, but that's a bother - explicitly passing the types doesn't work, because the type ofstatewould still be inferred:I could manually type out the entire return-type - that works, but it's a real bother:
I could break them apart like this:
That works, but two collections isn't what I wanted.
I could wrap them in a function and tease out the inference that way:
That also works, but, yuck...
I'm having two thoughts here.
💭 Having to create multiple, unrelated states at once might not be a good idea. I've pointed this out before, and I know you don't agree, because you're attached to the idea of object properties turning into debug-friendly names. I'm still not convinced of that, because the names will get mangled, and this won't save you when things fail in production - I'd prefer to find a more reliable approach to debugging.
💭 Alternatively, maybe the recommended idiom is to separate state from computed state, like in the two last examples - maybe you can argue that separating mutable from computed state is "a good thing".
If so, maybe we could have a separate helper, similar to
signal, for computed state only - this would just save you the repeatedwirecalls, when creating your derived states, but maybe it provides a more explainable concept?I'm not particularly fond of letting implementation issues drive design - but in this case, I suppose you could argue it's good to separate mutable state from computed state? 🤔
(This issue is partially me thinking ahead to documentation/tutorials - it would be great if we didn't need to explain problems like this, and even better if users didn't have to run into them and look for a solution and explanation in the first place...)