esch
v2.1.0
Published
A light-weight, powerful functional library
Downloads
11
Maintainers
Readme
Esch
Functional programming is powerful when not bogged down with preconcieved notions. Esch aims to be to the functional pattern what Mithril.js is to front-end frameworks. Tiny and powerful with just enough magic to make your life easier. Definitely not your Swiss army knife, but your 6" fixed blade knife that a survivalist would use.
Methods
Esch(data, ops...)
Esch(ops...)
returns an Esch chain, and you can pass in as many functions as you want or even compose another Esch instance
Esch.bind(data)
returns an Esch chain bound to a data-type, it would be the same as Esch(data)
Esch(data)( fn1 )( fn2 )()
invoking the Esch instance with any argument other than a function or array of functions says you want to compute the stack from Left -> Right
Esch(data)( fn1, fn2, fn3).apply.left()
Esch(data)( fn1, fn2, fn3).unravel()
convenience methods for invoking and Esch stack from Left -> Right
Esch(data)( fn1, fn2, fn3).apply.right()
A convenience method for invoking and Esch stack from Right -> Left
Esch()( fn1, promisedFn1, fn3)(data).then(handleDone).catch(handleError)
If Esch detects a promise in your operational stack, it upgrades the remaining functions to promised versions, and returns a promise that you can then can attack your usual promise handlers to.
Internals
I highly encourage you to read through the src/index.coffee
to see what is going on internally (it's only 92 lines).
It is very important to remember the the outer most context is the context that Esch is applied to
However, let's destructure what's going on a bit with this example:
const Esch = require('esch');
## some contrived utils
let utils = {}
utils.mapAdd = function (n){
return function(list){
return list.map(function(num){
return num + n
});
}
}
utils.mapMultiply = function (n){
return function(list){
return list.map(function(num){
return num * n
});
}
}
utils.mapSubtract = function (n){
return function(list){
return list.map(function(num){
return num - n
});
}
}
utils.reduce = function(list){
return list.map(function(memo, n){
return memo + n
});
}
var stack = Esch(); // returns an Esch.chainable bound to an empty object value
var reduction = stack( utils.mapAdd(4) ) // by passing in a Function we tell Esch we are still adding to the stack
( utils.mapMultiply(3) )
( utils.mapSubtract(1) )
( utils.reduce )
( [1,2,3,4] ); // By passing in something that isn't a function, we tell Esch to unravel our stack
// The default application is left -> right || top -> bottom
console.log(reduction); // 74
Here we are doing a very simple map reduce to our initial values.
When invoking Esch()
we are telling Esch we want a new stack of operations, and passing the context value [1,2,3,4]
through the manipulation pipeline.
There are a couple of different ways to achieve this same result, including merging contexts to create dynamic pipelines.
var tree = Esch(); // returns an Esch.chainable
var data = tree.bind([1,2,3,4]); // change the beginning context of our Esch.chainable stack
var amsr = tree(
utils.mapAdd(4),
utils.mapMultiply(3),
utils.mapSubtract(1),
);
l2r = data(amsr)(utils.reduce).apply.left(); // tell Esch we want a Left -> Right application
/*
Nesting
Internally Esch flattens these nested stack calls into a singular event chain\
*/
// tell Esch we want a Right -> Left application of our mapping functions
// then apply a Left -> Right reduction on the result
// ** JUST BECAUSE YOU CAN DOESN'T MEAN YOU SHOULD
var r2l = Esch(data(amsr)(amsr(amsr)).apply.right())(utils.reduce)();
console.log(r2l);
But wait... There is more!
Esch internally handles Promises as well...
var Promise = require('bluebird');
utils.fetch = function (route) {
return new Promise(function(resolve, reject){
return request(route, function(err, callback){
if (err) return reject(err);
return resolve(callback);
});
});
};
var stack = Esch();
var manipulate = stack( utils.fetch('/numbers') )
( utils.mapAdd(4) );
manipulate().then(function(result){
console.log(result);
});
What happens in this case is that Esch attempts to unravel the entire stack until it hits an operation that returns a Promise, it then forward maps the remainder of the stack to promises to ensure that the entirety of the stack will yield to the manipulation preceeding it.
The new promised operations forward its result to the next operation once resolved, and finally returns a promise that you use to then
on the stack's completion.
Think of it like this, where each step it a set of operations (since you can nest them recursively deep)
Stack:
Level 1 (fn1, fn2, fn3) * Promised Stack *
------- ------------------
| * fn4 is PROMISED * |
| Level 2 (fn4, fn5) fn5, fn6, ... fnN upgraded |
--------- ------------------------------
| |
| |
| Level 3 (fn6.. FnN) |
-----------------------
Once Esch determines the execution order, the entire stack is flattened and processed.
TODOS
- Add the ability to apply a Stream to a stack, so that as each object is emitted it is passed to the stack