Skip to content

Instantly share code, notes, and snippets.

@tosuke
Created October 11, 2021 12:40
Show Gist options
  • Save tosuke/3887f8ecd13ae266af44ef739eb5c96c to your computer and use it in GitHub Desktop.
Save tosuke/3887f8ecd13ae266af44ef739eb5c96c to your computer and use it in GitHub Desktop.
effectっぽいやつ
abstract class Effect<T> {
#Type!: T
}
type InferEffectType<TEffect> = TEffect extends Effect<infer T> ? T : never
class Nil extends Effect<void> {
#nominal!: true
}
type Eff<TEffects extends Effect<unknown>, T> = (run: EffRunner<TEffects>) => Promise<T>
type InferEffType<TEffects extends Effect<unknown>, E extends TEffects | Eff<TEffects, any>> = E extends Eff<any, infer T> ? T : E extends Effect<infer T> ? T : never
type EffRunner<TEffects extends Effect<unknown>> = <E extends TEffects | Eff<TEffects, any>>(effectOrEff: E) => InferEffType<TEffects, E>
function runEff<T>(eff: Eff<never, T>): Promise<T> {
const runner: EffRunner<Nil> = <U, E extends Nil | Eff<Nil, U>>(e: E) => {
if(e instanceof Nil) {
return Promise.resolve(void 0) as InferEffType<Nil, E>
}
return e(runner) as InferEffType<Nil, E>
}
return eff(runner)
}
const handleEff = <T, TEffects extends Effect<unknown>, TEffect extends TEffects>(
effectCtor: new (...args: any[]) => TEffect,
handler: (effect: TEffect) => Promise<InferEffectType<TEffect>>,
eff: Eff<TEffects, T>
): Eff<Exclude<TEffects, TEffect>, T> => async parentRunner => {
const isHandleEffect = (effect: TEffects): effect is TEffect => effect instanceof effectCtor
const runner: EffRunner<TEffects> = <U, E extends TEffects | Eff<TEffects, U>>(effectOrEff: E) => {
if(effectOrEff instanceof Effect) {
const effect = effectOrEff as TEffects
if(!isHandleEffect(effect)) {
// どうやっても型が通らない
return parentRunner(effect as any) as InferEffType<TEffects, E>
}
return handler(effect) as InferEffType<TEffects, E>
}
return effectOrEff(runner) as InferEffType<TEffects, E>
}
return await runner(eff)
}
class Ask extends Effect<string> {
#nominal!: true
constructor() {
super()
}
}
class Tell extends Effect<void> {
#nominal!: true
constructor(readonly message: string) {
super()
}
}
const tellN = (n: number, message: string): Eff<Tell, void> => async run => {
for(let i = 0; i < n; i++) {
await run(new Tell(message))
}
}
const program: Eff<Ask | Tell, string> = async run => {
const message = await run(new Ask())
await run(tellN(2, message))
return message
}
const program2 = handleEff(Tell, async (tellEffect) => {
console.log(tellEffect.message)
}, program)
const program3 = handleEff(Ask, async () => "hello", program2)
runEff(program3)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment