Created
May 10, 2021 16:10
-
-
Save Willmo36/4f34a9095b7a81d54b2cd8bc2323c4fd to your computer and use it in GitHub Desktop.
TypeScript GADT practice - Redux-saga like
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { hole, identity, pipe } from "@effect-ts/core/Function"; | |
/** Redux Types */ | |
/** Redux Types */ | |
/** Redux Types */ | |
type Reducer<S, A> = (s: S, a: A) => S; | |
type Store<S, A> = { | |
get: () => S; | |
dispatch: (a: A) => void; | |
}; | |
/** DSL types */ | |
/** DSL types */ | |
/** DSL types */ | |
type Effect<S, A, R> = | |
| Get<S, A, R> | |
| Push<S, A, R> | |
| Call<S, A, R> | |
| Delay<S, A, R> | |
| Chain<S, A, R>; | |
class Get<S, A, R> { | |
readonly _tag = "Get"; | |
constructor( | |
readonly use: <X>( | |
f: (_: { | |
readonly _S: (_: S) => S; | |
readonly _A: (_: A) => A; | |
readonly _R: (_: S) => R; | |
}) => X | |
) => X | |
) {} | |
} | |
class Push<S, A, R> { | |
readonly _tag = "Push"; | |
constructor( | |
readonly use: <X>( | |
f: <T>(_: { | |
readonly _S: (_: S) => S; | |
readonly _R: (_: unknown) => R; | |
readonly action: A; | |
}) => X | |
) => X | |
) {} | |
} | |
class Call<S, A, R> { | |
readonly _tag = "Call"; | |
constructor( | |
readonly promiseFn: () => Promise<R>, | |
readonly _S: (_: S) => S, | |
readonly _A: (_: A) => A | |
) {} | |
} | |
class Delay<S,A,R> { | |
readonly _tag = "Delay"; | |
constructor( | |
readonly ms: number, | |
readonly _R: (_: void) => R, | |
){} | |
} | |
class Chain<S, A, R> { | |
readonly _tag = "Chain"; | |
constructor( | |
readonly use: <X>( | |
go: <R2>(_: { | |
readonly inputEffect: Effect<S, A, R2>; | |
readonly afb: (r2: R2) => Effect<S, A, R>; | |
}) => X | |
) => X | |
) // readonly _S: (_: S) => S | |
{} | |
} | |
/** DSL consturctors */ | |
/** DSL consturctors */ | |
/** DSL consturctors */ | |
function makeConstructors<S, A>() { | |
function get(): Effect<S, A, S> { | |
return new Get<S, A, S>((go) => | |
go({ | |
_S: identity, | |
_A: identity, | |
_R: identity, | |
}) | |
); | |
} | |
function push(action: A): Effect<S, A, void> { | |
return new Push((go) => | |
go({ | |
_R: identity, | |
_S: identity, | |
action, | |
}) | |
); | |
} | |
function call<R>(promiseFn: () => Promise<R>): Effect<S, A, R> { | |
return new Call(promiseFn, identity, identity); | |
} | |
function delay(ms: number):Effect<S,A,void> { | |
return new Delay(ms, identity); | |
} | |
function chain<R1_, R2_>( | |
afb: (r1: R1_) => Effect<S, A, R2_> | |
): (effect: Effect<S, A, R1_>) => Effect<S, A, R2_> { | |
return (effect) => { | |
return new Chain<S, A, R2_>((g) => { | |
return g({ | |
inputEffect: effect, | |
afb, | |
}); | |
}); | |
}; | |
} | |
return { get, push, call, delay, chain }; | |
} | |
/** DSL interpreter */ | |
/** DSL interpreter */ | |
/** DSL interpreter */ | |
function run<S, A, R>(store: Store<S, A>, effect: Effect<S, A, R>): Promise<R> { | |
switch (effect._tag) { | |
case "Get": | |
return Promise.resolve(effect.use((f) => f._R(store.get()))); | |
case "Push": | |
return Promise.resolve( | |
effect.use((f) => { | |
store.dispatch(f.action); | |
return f._R({}); | |
}) | |
); | |
case "Call": | |
return effect.promiseFn(); | |
case "Delay": | |
return makeDelay(effect.ms).then(n => effect._R()) | |
case "Chain": | |
return effect.use(async (ops) => { | |
const r = await run(store, ops.inputEffect); | |
return run(store, ops.afb(r)); | |
}); | |
} | |
} | |
/** DSL testing */ | |
/** DSL testing */ | |
/** DSL testing */ | |
type S = { name: string }; | |
type Ac = { _tag: "capitalize" } | { _tag: "set_name"; payload: string }; | |
const capitalize = (): Ac => ({ _tag: "capitalize" }); | |
const setName = (name: string): Ac => ({ _tag: "set_name", payload: name }); | |
const testReducer: Reducer<S, Ac> = (state, action) => { | |
switch (action._tag) { | |
case "capitalize": | |
return { ...state, name: state.name.toUpperCase() }; | |
case "set_name": | |
return { ...state, name: action.payload }; | |
} | |
return state; | |
}; | |
let state: S = { name: "max" }; | |
const store: Store<S, Ac> = { | |
get: () => state, | |
dispatch: (action) => { | |
state = testReducer(state, action); | |
}, | |
}; | |
const makeDelay = (ms: number) => new Promise(res => { | |
setTimeout(() => { | |
res(void 0); | |
}, ms); | |
}); | |
const delayLazyPromise = <A>(fn: () => Promise<A>): () => Promise<A> => { | |
return () => makeDelay(3000).then(fn); | |
} | |
const { get, push, call, chain, delay } = makeConstructors<S, Ac>(); | |
const testProgram = pipe( | |
push(setName("Elmo")), | |
chain(() => push(capitalize())), | |
chain(() => get()), | |
chain(() => call(() => Promise.resolve(1232))), | |
chain(() => delay(1000)) | |
); | |
run(store, testProgram).then((result) => { | |
result; | |
const stateNow = store.get(); | |
stateNow; | |
console.log({stateNow, result}) | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment