Created
October 11, 2021 12:40
-
-
Save tosuke/3887f8ecd13ae266af44ef739eb5c96c to your computer and use it in GitHub Desktop.
effectっぽいやつ
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
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