Skip to content

Manage side-effects in your javascript application cleanly with algebraic effects

License

Notifications You must be signed in to change notification settings

phenax/algebraic-effects

Folders and files

NameName
Last commit message
Last commit date

Latest commit

0874ab2 · Jun 14, 2022
Mar 21, 2019
Jun 14, 2022
Sep 10, 2020
Feb 24, 2020
Feb 3, 2019
Jan 12, 2019
Jan 12, 2019
Jan 13, 2019
Jan 12, 2019
Sep 6, 2020
Feb 29, 2020
Feb 23, 2020
Feb 24, 2020
Feb 24, 2020
Feb 23, 2020
Feb 24, 2020

Repository files navigation

Algebraic Effects

Manage your effects in a pure and composible way using algebraic effects with multiple continuations. https://phenax.github.io/algebraic-effects

CircleCI npm bundle size (minified + gzip) Codecov

Donate using Liberapay Buy Me A Coffee donate button

Documentation

Install

To add the project to your project

yarn add @algebraic-effects/core

If you want effects like Exception, State, Random, etc.

yarn add @algebraic-effects/core @algebraic-effects/effects

Usage

Import it to your file

import { createEffect, func } from '@algebraic-effects/core';
import { sleep } from '@algebraic-effects/core/generic';

State effect counter example

import { State } from '@algebraic-effects/effects';
import { call, sleep } from '@algebraic-effects/core/generic';

const countdown = function*() {
  const count = yield State.get();

  if(count > 0) {
    yield State.set(count - 1); // Decrement count
    yield sleep(1000); // Add a delay of 1 second
    yield call(countdown); // Recursively call the program again.
  }
}

State.of(10)(countdown)
  .fork(() => {}, () => alert('HAPPY NEW YEAR!!!!'));

Creating your own effects

  • Declare your effects
import { createEffect, func } from '@algebraic-effects/core';

export const ConsoleEffect = createEffect('ConsoleEffect', {
  log: func(['...data']),
});

export const ApiEffect = createEffect('ApiEffect', {
  fetchUser: func(['userid'], 'user'),
  markUserAsViewed: func(['userid']),
});

func function allows you to document the operation signature.

  • Write your program
const fetchProfile = function*(uid) {
  const user = yield ApiEffect.fetchUser(uid);

  yield ConsoleEffect.log('>> Fetched user user', uid);

  if(user.isPublic) {
    yield ApiEffect.markUserAsViewed(user.id);
    yield ConsoleEffect.log('>> Marked', uid, 'as viewed');
    return user;
  }

  return { id: uid, name: user.name, isPrivate: true };
}
  • Implement effect operation behavior
const logger = ConsoleEffect.handler({
  log: ({ resume }) => (...args) => {
    console.log(...args);
    resume();
  },
});

const api = ApiEffect.handler({
  markUserAsViewed: ({ resume, throwError }) =>
    uid => fetchJson(`/user/${uid}/mark-as-viewed`).then(resume).catch(throwError),
  fetchUser: ({ promise }) => uid => promise(fetchJson(`/user/${uid}`)),
});

promise is a shorthand for doing .then(resume).catch(throwError)

  • Calling your program
api.with(logger) // Compose your effect handlers together and run them
  .run(fetchProfile)
  .fork(
    e => { /* Handle error */ },
    user => { /* Handle success */ }
  )

Multiple continuations

You can call resume multiple times from your operation synchronously.

function flipCoins() {
  const isHead1 = yield Random.flipCoin(2);
  const isHead2 = yield Random.flipCoin(2);
  return [isHead1 ? 'H' : 'T', isHead2 ? 'H' : 'T'];
}

// // runMulti method will start your program in multiple continuations mode
Random.seed(10)
  .runMulti(flipCoins)
  .fork(identity, data => {
    console.log(data); // Probably [[H, T], [H, T], [T, H], [T, T]]
  });
Writing custom effect with multiple continuations
const ListEffect = createEffect('ListEffect', {
  takeItem: func(['list'], '*', { isMulti: true }), // isMulti flag indicates that this operation resumes multiple times
});

// Program will resolve with [3, 4, 6, 7]
function *program() {
  const item1 = yield ListEffect.takeItem([ 1, 4 ]);
  const item2 = yield ListEffect.takeItem([ 2, 3 ]);

  return item1 + item2;
}

const looper = ListEffect.handler({
  takeItem: ({ resume }) => list => list.forEach(resume),
});

// runMulti method will start your program in multiple continuations mode
looper.runMulti(program).fork(
  handleError,
  data => {
    console.log(data); // [3, 4, 6, 7]
  }
);

Contributing

License

Algebraic effects is under MIT licensed.