SkillAgentSearch skills...

Effectfuljs

JavaScript embedded effects compiler

Install / Use

/learn @awto/Effectfuljs
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

JavaScript embedded effects compiler

Build Status

This is a tool to build JavaScript to JavaScript transpilers (babel plugins) along with a few predefined ones. It extends JavaScript language with various computation effects by means of runtime libraries but without any syntax extension.

There are such libraries for:

Not yet implemented:

  • algebraic effects
  • probabilistic programming
  • parallel and distributed programming
  • persistent continuations
  • adaptive computations
  • logical programming - Old version
  • reactive programming (with RxJS) - Old version

They are typically small libraries, some of them are just tiny wrappers around well-known interfaces, such as Promises or Observables.

The compiler converts ES2018 to ES2018 and doesn't need any syntax extensions. So it may be applied to results of other compilers targeting JavaScript such as CoffeeScript, TypeScript etc.

Usage

EffectfulJS is a tool to build transpilers. There are many packaging options and usages, but typically the follow next pattern. Transformations usually contain a babel plugin (and/or macro) along with some runtime. They can be in a single package, with Effectful to be a peer dependency (along with a dev dependency). This way the transpiler can be installed with:

$ npm install --save <lib name>
$ npm install --save-dev @effectful/core

If the babel plugin is in transform.js of <lib name> package, .babelrc can be:

{
   "plugins": [<lib name>/transform]
}

The package can also contain macro.js file for zero-configuration using babel-plugin-macros. This works also for Create Reat App) where .babelrc is disabled.

Check the libraries documentation for exact details:

  • @effectful/js compiles to the most abstract code, single or double level depending on parameters
  • @effectful/es for two-levels syntax either concrete ECMAScript compatible effects or abstract for async, generator and async generators semantics overloading.
  • @effectful/es-inline A shortcut for @effectful/es with concrete ECMAScript effects implementations.

All the plugins and macros are just shortcuts to @effectful/core setting needed lower level options described in config.js thus generated code uses different runtime interfaces.

Examples

These examples are not the full list of features. The project is abstract, with a lot of different applications.

Persistent state

Save all your application state in DB or file. The state includes the point where it executes now along with all variables values implementing any serialization interface.

Restore the saved state after, maybe in a different process or even on a different hardware.

For the application, the state transfer will be transparent.

This feature greatly simplifies defining long-running workflows. If the program requires some not immediate action from a human, e.g. email-confirmation, or it should await some exact date. No needs to bother implementing all these details, just write a very simple script describing the business logic and nothing else.

Here's some shop business logic example.

function order(customer, item) {
  begin();
  customer.balance -= item.price;
  item.vendor.balance += item.price;
  scheduleShipment(customer, item);
  commit();
}

The scheduleShipment function may require some other people involved. They can reject the transaction and so the balances values must be reset to original levels, even if a few days already passed. We don't care about implementation details. Such as where and how to store the state, how to reset it, etc. Everything is done by some underlying generic framework. The framework is not a subject of EffectfulJS though.

There are a lot of other applications for a persistent state. It can be used also for debugging purposes. This makes it very simple to implement hot reloading and time traveling, as this just saving and restoring the application state at some particular execution points.

More details are in @effectful/es project.

Delimited continuations

This is the most generic computational effect. Any other can be implemented using it. It looks very similar to Coroutines (async functions and generators). But unlike Coroutines, it allows resuming computation from the same point more than once.

Unlimited continuation like callcc is a special case. They allow capturing all and only all execution context. While with delimited continuations it is possible to specify only some region. For example, for the persistent state, this allows saving not the whole application state, which may be big, but only some interesting part.

Check @effectful/cc for more details.

Implicit parallelism

Async functions considerably simplify asynchronous source code. But it serializes operation execution. Next operation executes only after the current is finished. If we want to run the operations in parallel we can use Promise.all.

Say, we have two async operations (probably some remote server requests) - A and B:

async function a() {
  const a = await A;
  const b = await B;
}

If these operations are independent they can be executed in parallel:

async function a() {
  const [a, b] = await Promise.all([A, B]);
}

This doesn't look more complex, but what if we make B dependent on A and add another operation C:

async function parDemo() {
  const a = await A;
  const b = await B(a);
  const c = await C;
}

Now it is impossible to start B before A is finished, but we can start C in parallel with A and B:

async function parDemo() {
  const [a, c] = await Promise.all([A, C]);
  const b = await B(a);
}

This version is not effective, operation C may take much longer than A and this will prevent B from starting execution. The correct version is:

async function parDemo() {
  let a;
  const [b, c] = await Promise.all([
    (async () => {
      a = await A;
      b = await B(a);
    })(),
    C
  ]);
}

This is obviously significantly more complex, error-prone, harder to read and maintain. It will become even much worse with some next small changes. They may require almost full function rewrite.

Fortunately, EffectfulJS can do the transformation automatically, just specify which block you want to parallelize. The transpiler will automatically compute variables dependencies and structure the block parts in Promise.all accordingly.

Here is how the last example looks with EffectfulJS:

async function parDemo() {
  "par";
  const a = await A;
  const b = await B(a);
  const c = await C;
}

Switching between parallel and sequential code is just a matter of adding "seq"/"par" directives.

This works for loops too. With the help of a persistent state some jobs may be delegated to WebWorker or some cloud server, making simple but effective distributed programs.

Reactive programming (WIP)

Reactive programs operate with values which change with time. While in the source code it looks like a single value variable:

function drawBox(root) {
  const down = event(root, "mousedown");
  if (down && down.button === 0) {
    const move = event(root, "mousemove");
    const up = event(root, "up");
    const cur = move || up;
    const box = (
      <div className={up ? "box" : "drawing"}>
        style=
        {{
          left: Math.min(down.x, cur.x) + pageXOffset,
          top: Math.min(down.y, cur.y) + pageYOffset,
          width: Math.abs(down.x - cur.x),
          height: Math.abs(down.y - cur.y)
        }}
        >
      </div>
    );
    const del = event(box, "contextMenu");
    if (!del) root.appendChild(box);
    if (!move) return;
  }
}

Here all the variables may change when some mouse event is received, while the program looks like it is executed only once. The framework is responsible for recalculating dependant parts of the program. Such programs are much easier to write, test, debug and maintain.

Logical programming (WIP)

This is a library for adding logical programming languages features to JavaScript. Most Prolog books start from classical bi-directional list append function. Depending on input arguments it may either append two lists or split some list in two. Here is an almost literal translation of its implementation in JavaScript:

function append(a, b, r) {
  const [h, t, rt] = newRefs();
  unify(a, cons(h, t));
  unify(r, cons(h, rt));
  append(t, b, rt);
  M.or();
  unify(a, nil());
  unify(r, b);
}

For front-end, this, for example, can be useful to specify some control's values constraints. In the next example, there are three cyclically depe

View on GitHub
GitHub Stars343
CategoryDevelopment
Updated1mo ago
Forks15

Languages

JavaScript

Security Score

100/100

Audited on Feb 25, 2026

No findings