Skip to content

Instantly share code, notes, and snippets.

@safareli
Last active May 4, 2020 18:30
Show Gist options
  • Save safareli/4e0f8fcc85065c270d7269828cc306f3 to your computer and use it in GitHub Desktop.
Save safareli/4e0f8fcc85065c270d7269828cc306f3 to your computer and use it in GitHub Desktop.
// prototype of making effect Readerish
// https://gist.github.com/safareli/7f60873324cce8880fb4cfa85defe4df
export interface EffectRaw<E,A> { (env: E): A }
interface EffectPURE<E, A> extends EffectRaw<E,A> {
tag: "PURE",
_0: A
}
interface EffectMAP<E, Z, A> extends EffectRaw<E, A>, EffectMAP_OP<Z, A> {
_1: Effect<E, Z>
}
interface EffectAPPLY<E, Z, A> extends EffectRaw<E, A>, EffectAPPLY_OP<E,Z> {
_1: Effect<E, ((val: Z) => A)>
}
interface EffectBIND<E, Z, A> extends EffectRaw<E, A>, EffectBIND_OP<E,Z, A> {
_1: Effect<E, Z>
}
interface EffectLOCAL<E, Z, A> extends EffectRaw<E, A>, EffectLOCAL_OP<E,Z> {
_1: Effect<Z, A>
}
export type Effect<E, A> = EffectRaw<E, A> | EffectTagged<E, A>
type EffectTagged<E, A>
= EffectPURE<E, A>
| EffectMAP<E, unknown, A>
| EffectLOCAL<E, unknown, A>
| EffectAPPLY<E, unknown, A>
| EffectBIND<E, unknown, A>
export const pure = function <E,A>(x: A): EffectPURE<E,A> {
const res: EffectPURE<E,A> = function $effect(env: E): A { return runEff($effect, env); };
res.tag = "PURE";
res._0 = x;
return res;
};
export const map = function <E,Z, A>(f: ((val: Z) => A)) {
return function (effect: Effect<E,Z>): EffectMAP<E,Z, A> {
const res: EffectMAP<E,Z, A> = function $effect(env: E): A { return runEff($effect, env); };
res.tag = "MAP";
res._0 = f;
res._1 = effect;
return res;
};
};
export const apply = function <E,Z, A>(effF: Effect<E,((val: Z) => A)>) {
return function (effect: Effect<E,Z>): EffectAPPLY<E,Z, A> {
const res: EffectAPPLY<E,Z, A> = function $effect(env: E): A { return runEff($effect, env); };
res.tag = "APPLY";
res._0 = effect;
res._1 = effF;
return res;
};
};
export const bind = function <E,Z, A>(effect: Effect<E,Z>) {
return function (f: (val: Z) => Effect<E,A>): EffectBIND<E,Z, A> {
const res: EffectBIND<E,Z, A> = function $effect(env: E): A { return runEff($effect, env); };
res.tag = "BIND";
res._0 = f;
res._1 = effect;
return res;
};
};
export const ask: <E>() => Effect<E, E> = () => <E>(env: E): E => env;
export const local = function <E,Z, A>(f: ((env: E) => Z)) {
return function (effect: Effect<Z,A>): EffectLOCAL<E, Z, A> {
const res: EffectLOCAL<E, Z, A> = function $effect(env: E): A { return runEff($effect, env); };
res.tag = "LOCAL";
res._0 = f;
res._1 = effect;
return res;
};
};
interface EffectAPPLY_FUNC_OP<Z, A> { tag: "APPLY_FUNC", _0: (v: Z) => A }
interface EffectMAP_OP<Z, A> { tag: "MAP", _0: (v: Z) => A }
interface EffectAPPLY_OP<E, Z> { tag: "APPLY", _0: Effect<E, Z> }
interface EffectBIND_OP<E, Z, A> { tag: "BIND", _0: (v: Z) => Effect<E, A> }
interface EffectLOCAL_UNDO_OP<Z> { tag: "LOCAL_UNDO", _0: Z }
interface EffectLOCAL_OP<E,Z>{ tag: "LOCAL", _0: (env: E) => Z }
type Effect_OP<E, Z, A>
= EffectAPPLY_FUNC_OP<Z, A>
| EffectMAP_OP<Z, A>
| EffectLOCAL_OP<E, Z>
| EffectLOCAL_UNDO_OP<Z>
| EffectAPPLY_OP<E, Z>
| EffectBIND_OP<E, Z, A>
function runEff<E, A>(inputEff: Effect<E, A>, env: E): A {
const operations: Array<Effect_OP<E, unknown, unknown>> = [];
let effect: Effect<E,unknown> = inputEff;
let res: unknown | undefined;
let op: Effect_OP<E,unknown, unknown> | undefined;
effLoop: for (; ;) {
if ("tag" in effect) {
switch (effect.tag) {
case "MAP":
operations.push(effect);
effect = effect._1;
continue;
case "LOCAL":
operations.push(effect);
effect = effect._1;
break;
case "BIND":
operations.push(effect);
effect = effect._1;
continue;
case "APPLY":
operations.push(effect);
effect = effect._1;
continue;
case "PURE":
res = effect._0;
break;
}
} else {
res = effect(env);
}
while ((op = operations.pop())) {
switch (op.tag) {
case "MAP":
res = op._0(res);
break
case "LOCAL":
operations.push({ tag: "LOCAL_UNDO", _0: env});
env = op._0(env) as any;
continue effLoop;
case "LOCAL_UNDO":
env = op._0 as any;
break
// or maybe:
// continue effLoop;
case "APPLY_FUNC":
res = op._0(res);
break
case "APPLY":
effect = op._0;
operations.push({ tag: "APPLY_FUNC", _0: res as (v: unknown) => unknown });
continue effLoop;
case "BIND":
effect = op._0(res);
continue effLoop;
}
}
return res as A;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment