Skip to content

susisu/effectful

Repository files navigation

@susisu/effectful

CI

npm i @susisu/effectful
# or
yarn add @susisu/effectful

Example

import { Effectful, makeEffect, run } from "@susisu/effectful";

// 1. Augment `EffectDef<A>` to define new kinds of effects

declare module "@susisu/effectful" {
  interface EffectDef<A> {
    "state/get": {
      k: (x: number) => A; // constrains A = number
    };
    "state/put": {
      value: number;
      k: (x: undefined) => A; // constrains A = undefined
    };
  }
}

// 2. Define effect constructors using `makeEffect`

const getState = makeEffect<"state/get", number>("state/get", {
  k: x => x,
});

const putState = (value: number) =>
  makeEffect<"state/put", undefined>("state/put", {
    value,
    k: x => x,
  });

// 3. Write effect handlers

function runState<T>(
  comp: Effectful<"state/get" | "state/put", T>,
  state: { current: number }
): T {
  return run(comp, x => x, {
    "state/get": (eff, resume) => {
      return resume(eff.value.k(state.current));
    },
    "state/put": (eff, resume) => {
      state.current = eff.value.value;
      return resume(eff.value.k(undefined));
    },
  });
}

// 4. Write computations using generators

function* getAndMultiplyState(
  multiplier: number
): Effectful<"state/get", number> {
  // use `yield*` to perform an effect
  const value = yield* getState;
  return multiplier * value;
}

function* main(): Effectful<"state/get" | "state/put", string> {
  // computations can be nested
  const newValue = yield* getAndMultiplyState(3);
  yield* putState(newValue);
  return `current state is ${newValue}`;
}

// 5. Run the computation

const state = { current: 2 };
const result = runState(main(), state);
console.log(result);
// => "current state is 6"
console.log(state);
// => { current: 6 }

License

MIT License

Author

Susisu (GitHub, Twitter)

Prior works