Open getify opened 2 years ago
Here's an alternate approach that I think addresses some of the above limitations:
doWithState(component).run({});
function *component({ useState, }) {
var [ id, updateID ] = yield useState(() => Math.floor(Math.random() * 1000));
console.log(`random ID: ${id}`);
id = yield updateID(42);
console.log(`fixed ID: ${id}`);
}
And the implementation of doWithState(..)
:
function doWithState(gen,...args) {
var state = { nextSlotIdx: 0, slots: [], };
return IO(env => {
state.nextSlotIdx = 0;
var context = { ...env, useState, };
return IO.do(gen,...args).run(context);
});
// ********************************************
function useState(initialVal) {
return IO(env => {
var slots = state.slots;
var curSlotIdx = state.nextSlotIdx++;
if (!(curSlotIdx in slots)) {
if (typeof initialVal == "function") {
initialVal = initialVal();
}
slots[curSlotIdx] = initialVal;
}
return [
slots[curSlotIdx],
function update(nextVal){
return IO(() => {
if (typeof nextVal == "function") {
nextVal = nextVal(slots[curSlotIdx]);
}
return (slots[curSlotIdx] = nextVal);
});
},
];
});
}
}
A further refinement to the doWithState(..)
approach, that seems to solve all the remaining issues as listed earlier... provides a useState(..)
for accessing private state via numerically indexed slots, and useSharedState
for accessing shared state via named slots, both of which are still fully self-contained in the provided IO reader-env object:
doWithState(component).run({});
function *component({ useState, useSharedState, }) {
var [ id, updateID ] = yield useState(() => Math.floor(Math.random() * 1000));
var [ greeting, updateGreeting ] = yield useSharedState("greeting","Hello World!");
console.log(`random ID: ${id}`);
id = yield updateID(42);
console.log(`fixed ID: ${id}`);
console.log(greeting);
}
And this implementation:
const privateStateKeys = new WeakMap();
const SHARED_STATE = Symbol("shared-state");
function doWithState(gen,...args) {
return withState(IO.do(gen,...args),gen);
}
function doXWithState(gen,deps,...args) {
return withState(IOx.do(gen,deps,args),gen);
}
function withState(io,gen) {
if (!privateStateKeys.has(gen)) {
privateStateKeys.set(gen,Symbol(`private-state:${gen.name || gen.toString()}`));
}
const PRIVATE_STATE = privateStateKeys.get(gen);
return IO(env => {
var {
[PRIVATE_STATE]: privateState = { nextSlotIdx: 0, slots: [], },
[SHARED_STATE]: sharedState = {},
} = env;
env[PRIVATE_STATE] = privateState;
env[SHARED_STATE] = sharedState;
privateState.nextSlotIdx = 0;
return io.run({
...env,
useState,
useSharedState,
});
});
// ********************************************
function useState(initialVal) {
return IO(({ [PRIVATE_STATE]: privateState, }) => {
return accessState(
privateState.slots,
privateState.nextSlotIdx++,
initialVal,
env => env[PRIVATE_STATE].slots
);
});
}
}
function useSharedState(stateName,initialVal) {
return IO(({ [SHARED_STATE]: sharedState, }) => {
return accessState(
sharedState,
stateName,
initialVal,
env => env[SHARED_STATE]
);
});
}
function accessState(stateStore,prop,initialVal,getStateStore) {
if (!(prop in stateStore)) {
if (typeof initialVal == "function") {
initialVal = initialVal();
}
stateStore[prop] = initialVal;
}
return [
stateStore[prop],
function update(nextVal){
return IO(env => {
var stateStore = getStateStore(env);
if (typeof nextVal == "function") {
nextVal = nextVal(stateStore[prop]);
}
return (stateStore[prop] = nextVal);
});
}
];
}
Taking inspiration from Hooks (i.e., React), a state preserving mechanism like
useState(..)
is proposed.You could use it in do-routines like this:
There are some unresolved issues with such a mechanism:
nextSlotIdx
needs some way to be reset between invocations of thecomponent(..)
Here's a candidate first implementation: