Skip to content

Commit

Permalink
TOSQUASH
Browse files Browse the repository at this point in the history
  • Loading branch information
elierotenberg committed Aug 11, 2017
1 parent c1be2e7 commit f6bc650
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 0 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Userland algebraic effects in JS
================================

This repository is highly experimental and is absolutely not production ready.

It is a tentative implementation of the concepts behind algebraic effects in JS.

### The Problem and how Algebraic Effects can help

The JS computation model basically works the following way:
- JS has a task queue, and runs tasks to completion.
- Tasks can be scheduled either directly by the host environment (browser, node) in reaction to native events: `postmessage`, `fs.readFile` returning, etc, or indirectly when JS calls scheduling functions such as `setTimeout` or `new Promise`. [1]

At any point in time, JS is either idle, or running a synchronous task. JS itself isn't capable of doing asynchronous work - it merely delegates asynchronous tasks to the host environment, which itself does async work in the background, and enqueues a new task to handle the results (usually using callbacks).

The problem is that




[1] Scheduling is actually more complex since there are actually multiple tasks queues and a priority system, depending on the host environment, but for all intents and purposes here, the task queue is opaque.
82 changes: 82 additions & 0 deletions src/Membrane.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import asyncHooks from 'async_hooks';
import fs from 'fs';
import util from 'util';

const membranes = new Map();
const resources = new Map();

// We use process.nextTick as a scheduler, but maybe new Promise or other microtask scheduler can be used elsewhere
const scheduleMicroTask = fn => process.nextTick(fn);

const debugLog = (...args) =>
fs.writeSync(
1,
`[executionAsyncId=${asyncHooks.executionAsyncId()}] ${util.format(
...args,
)}\n`,
);

class Resource {
constructor(resourceId, type, parentResourceId, membraneId) {
this.resourceId = resourceId;
this.type = type;
this.parentResourceId = parentResourceId;
this.membraneId = membraneId;
}
}

class Membrane {
constructor(membraneId, name) {
this.membraneId = membraneId;
this.name = name;
}
}

const findMembraneId = parentResourceId => {
const membrane = membranes.find(parentResourceId);
if (membrane) {
return membrane.membraneId;
}
const parentResource = resources.find(parentResourceId);
if (!parentResource) {
return null;
}
return parentResource.membraneId;
};

const init = (resourceId, type, parentResourceId) => {
const membraneId = findMembraneId(parentResourceId);
if (membraneId !== null) {
const resource = new Resource(
resourceId,
type,
parentResourceId,
membraneId,
);
resources.set(resourceId, resource);
}
};

const destroy = resourceId => {
if (resources.has(resourceId)) {
resources.delete(resourceId);
}
if (membranes.has(resourceId)) {
membranes.delete(resourceId);
}
};

const fork = (name, fn) => {
const parentResourceId = asyncHooks.executionAsyncId();
scheduleMicroTask(() => {
const membraneId = asyncHooks.executionAsyncId();
membranes.set(membraneId, new Membrane(membraneId, name, parentResourceId));
fn();
});
};

const asyncHook = asyncHooks.createHook({ init, destroy });

asyncHook.enable();

export { fork, debugLog };
53 changes: 53 additions & 0 deletions src/asynchooks.example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import asyncHooks from 'async_hooks';
import util from 'util';
import fs from 'fs';

let asyncHook = null;

let resourcesMap = new Map();

const log = (...args) =>
fs.writeSync(
1,
`[executionAsyncId=${asyncHooks.executionAsyncId()}] ${util.format(
...args,
)}\n`,
);

const getRootAsyncId = (asyncId = asyncHooks.executionAsyncId()) => {
if (!resourcesMap.has(asyncId)) {
return asyncId;
}
return getRootAsyncId(resourcesMap.get(asyncId).parent);
};

const init = (asyncId, type, triggerAsyncId) => {
resourcesMap.set(asyncId, {
parent: triggerAsyncId,
root: getRootAsyncId(triggerAsyncId),
});
log('init', { asyncId, type, triggerAsyncId, rootAsyncId: getRootAsyncId() });
};
const destroy = asyncId => log('destroy', { asyncId });

const install = () => {
if (asyncHook) {
return;
}
asyncHook = asyncHooks.createHook({ init, destroy });
asyncHook.enable();
};

install();

log('outside everything (top level)');
setTimeout(() => {
log('inside first setTimeout');
setTimeout(() => {
log('inside second setTimeout');
}, 1);
}, 1);
process.nextTick(() => {
log('inside first process.nextTick');
process.nextTick(() => log('inside second process.nextTick'));
});
2 changes: 2 additions & 0 deletions src/intercept.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const print = effect('print');
const sleep = effect('sleep');
const get = effect('get');
const set = effect('set');
const notImplemented = effect('notImplemented');

function* initializeStores(values) {
for (const key of Object.keys(values)) {
Expand All @@ -29,6 +30,7 @@ function* doWork() {
})(function*() {
yield sleep(1000);
yield* displayStores(['foo', 'fizz']);
yield notImplemented();
return yield Promise.resolve(42);
});
}
Expand Down
7 changes: 7 additions & 0 deletions src/membrane.example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Membrane } from './Membrane';

(async () => {
const membrane = new Membrane('async');
await Promise.resolve();
membrane.destroy();
})();

0 comments on commit f6bc650

Please sign in to comment.