(function() {
Lemonad.js http://www.functionaljs.org (c) 2013 Fogus, Ariadne Softworks Lemonad may be freely distributed under the MIT license
Always start with the secret JavaScript sauce...
(function() {
I want to require Underscore.js if it's not already available.
var _ = this._ || require('underscore');
Establish the root object, window
in the browser, or global
on the server.
var root = this;
The namespace object L
is a function in its own right that returns
a partially applied function of an arbitrary number of arguments.
For example, you can use L
to partially apply the first argument to the
_.template
function:
var hi = L(_.template, "Hello <%= name %>");
_.map([{name: 'Joe'}, {name: 'Bob'}, {name: 'Cleveland'}], hi);
//=> ["Hello Joe", "Hello Bob", "Hello Cleveland"]
signature: (any* -> a) -> any* -> (any* -> a)
var L = function(fun /*, args */) {
var args = _.rest(arguments);
if (_.some(args)) {
Default to the native bind
if available
if (_.exists(fun.bind)) {
return fun.bind.apply(fun, _.cons(undefined, args));
}
return function(/* arguments */) {
return fun.apply(null, args.concat(_.toArray(arguments)));
};
}
else return L.curry(fun);
};
Current version
L.VERSION = '0.6.2';
A simple function to throw an Error
function fail(str) {
throw new Error(str);
}
A temporary way to derive the name of a function. I hope to eliminate the need sooner rather than later.
signature: (a -> b) -> string
function nom(fun) {
var src = fun.toString();
src = src.substr('function '.length);
var n = src.substr(0, src.indexOf('('));
if (!_.isFunction(fun)) fail("Cannot derive the name of a non-function.");
if (fun.name && (fun.name === n)) return fun.name;
if (n) return n;
fail("Anonymous function has no name.");
}
Delegates down to the snapshot method if an object has one or clones the object outright if not.
var snapshot = function(thing) {
if (_.exists(thing) && _.exists(thing.snapshot))
return thing.snapshot();
else
return _.snapshot(thing);
};
Used as a unique value to signify certain conditions in Lemonad. Also takes a number of args and returns an array of unique values.
signature: any* -> [any*]
L.$ = function(){
return _.uniq(arguments);
};
L.inc = function(n) { return n+1; };
L.dec = function(n) { return n-1; };
L.halve = function(n) { return n/2; };
L.ctor = function(obj) {
return _.exists(obj) ? obj.constructor : null;
};
L.isReference = function(x) {
return (x instanceof L.Hole) || (x instanceof L.CAS);
};
Returns a function that will invoke the given method on any object that it receives as an argument.
L.meth = L.walterWhite = L.invoker = function(method /*, args*/) {
var pargs = _.rest(arguments);
return function(target /* args ... */) {
if (!_.exists(target)) {
return undefined;
}
var methodName = nom(method).trim();
var targetMethod = target[methodName];
var args = _.rest(arguments);
if (_.exists(targetMethod) && (method === targetMethod)) {
return targetMethod.apply(target, _.cat(pargs, args));
}
else {
return undefined;
}
};
};
Builds a polymorphic function based on dispatch predicates.
L.dispatcher = function (/* funs */) {
var funs = _.toArray(arguments);
var size = funs.length;
return function(target /*, args */) {
var ret = undefined;
var args = _.rest(arguments);
for (var funIndex = 0; funIndex < size; funIndex++) {
var fun = funs[funIndex];
ret = fun.apply(fun, cons(target, args));
if (existy(ret)) return ret;
}
return ret;
};
};
Curries a function to one parameter.
L.curry = L.curry1 = L.fix1 = L.schonfinkel = function(fun) {
return function(arg) {
return fun(arg);
};
};
Curries a function of two parameters from right to left.
L.curry2 = L.fix2 = L.schonfinkel2 = function(fun) {
return function(last) {
return function(first) {
return fun(first, last);
};
};
};
Curries a function of three parameters from right to left.
L.curry3 = L.fix3 = L.schonfinkel3 = function(fun) {
return function(last) {
return function(middle) {
return function(first) {
return fun(first, middle, last);
};
};
};
};
Curries a function of four parameters from right to left.
L.curry4 = L.fix4 = L.schonfinkel4 = function(fun) {
return function(fourth) {
return function(third) {
return function(second) {
return function (first) {
return fun(first, second, third, fourth);
};
};
};
};
};
parses ints
L.parseInt = L.curry(parseInt);
greater-than and less-than
L.gt = L.curry2(function(lhs, rhs) { return lhs > rhs; });
L.lt = L.curry2(function(lhs, rhs) { return lhs < rhs; });
L.eq = L.curry2(function(lhs, rhs) { return _.isEqual(lhs, rhs); });
L.is = L.curry2(function(lhs, rhs) { return lhs === rhs; });
L.oneOf = L.curry2(function(lhs, set) { return _.some(set, L.eq(lhs)); });
Partially applies a function by one argument from left to right
L.partial1 = function(fun, arg1) {
return function() {
return fun.apply(null, _.cons(arg1, _.toArray(arguments)));
};
};
Partially applies a function by two arguments from left to right
L.partial2 = function(fun, arg1, arg2) {
return function() {
return fun.apply(null, _.cat([arg1, arg2], _.toArray(arguments)));
};
};
Rotates the arguments of a function. For example: L.rot(fun(a, b, c)) => fun(b, c, a)
L.rot = function(fun) {
return function(/* args */) {
var head = _.first(arguments);
var more = _.rest(arguments);
return fun.apply(fun, _.cat(more, [head]));
};
};
Creates a function that works within the context of the
actions
logic. lift
accepts one or two functions:
If a state function is not given then the result given by the answer function serves as the state value at each step.
L.lift = function(answerFun, stateFun) {
return function(/* args */) {
var args = _.toArray(arguments);
return function(state) {
var ans = answerFun.apply(null, _.cons(state, args));
If no stateFun given, just carry the answer forward
var s = _.exists(stateFun) ? stateFun(state) : ans;
return {answer: ans, state: s};
};
};
};
Takes a number of curried functions that modifies an intermediate description of mutation. The final function in the sequence is meant to return the final value in the chain.
L.actions = function (acts, done) {
return function (seed) {
var init = { values: [], state: seed };
var intermediate = _.reduce(acts, function (stateObj, action) {
var result = action(stateObj.state);
var values = _.cat(stateObj.values, [result.answer]);
return { values: values, state: result.state };
}, init);
var keep = _.filter(intermediate.values, _.exists);
return done(keep, intermediate.state);
};
};
Mixes into a target object (destructively)
L.mix = function(target /*, mixins */) {
var args = _.rest(arguments);
if (_.some(args))
_.extend.apply(null, _.cons(target, args));
return target;
};
The Watchable mixin encapsulates the Subscriber/Observer pattern
L.WatchableMixin = (function () {
var watchers = {};
return {
The use of named function exprs is a temporary hack to make
these methods play nicely with invoker
. This is a non-portable
technique.
notify: function notify(oldVal, newVal) {
_.each(watchers, function(watcher, key) {
watcher.call(this, key, oldVal, newVal);
});
return _.size(watchers);
},
The watch function accepts a callback and a descriptive key used for associating the callback with a name of some sort.
watch: function watch(key, fun) {
watchers[key] = fun;
return key;
},
Takes the key and unsubscribes the watcher associated with it.
unwatch: function unwatch(key) {
var old = watchers[key];
watchers = _.omit(watchers, key);
return old;
}
};
})();
The functions for use with Watchable things
L.addWatch = L.meth(L.WatchableMixin.watch);
L.removeWatch = L.meth(L.WatchableMixin.unwatch);
A hole is a mutable cell: Takes an initial value Accepts an optional validator
L.Hole = function(init, validator) {
if (validator && !validator(init))
fail("Attempted to set invalid value " + init + " on construction");
this._value = init;
this._validator = validator;
};
The Hole mixin describes the following change policy: All value setting goes through the validator (if available) Swaps take a function that accepts the current value and set the new value with its result * Snapshots should be values
L.HoleMixin = {
setValue: function setValue(newVal) {
var validate = this._validator;
var oldVal = this._value;
if (_.exists(validate))
if (!validate(newVal))
fail("Attempted to set invalid value " + newVal);
this._value = newVal;
this.notify(oldVal, newVal);
return this._value;
}
};
L.SwapMixin = {
swap: function swap(fun /* , args... */) {
return this.setValue(fun.apply(this, _.cons(this._value, _.rest(arguments))));
},
Delegate down to the snapshot function
snapshot: function snapshot() {
return snapshot(this._value);
}
};
L.mix(L.Hole.prototype,
L.HoleMixin,
L.SwapMixin,
L.WatchableMixin);
The function delegates.
L.setValue = L.meth(L.Hole.prototype.setValue);
L.swap = L.meth(L.Hole.prototype.swap);
A CAS is a Hole with compare-and-swap semantics
L.CAS = function() {
L.Hole.apply(this, arguments);
};
L.CASMixin = {
A compareAndSwap expects the caller to supply a value that it expects is the current value. Change fails if these values do not match.
compareAndSwap: function compareAndSwap(oldVal, f) {
if (this._value === oldVal) {
this.setValue(f(this._value));
return true;
}
else {
return undefined;
}
}
};
L.mix(L.CAS.prototype,
L.CASMixin,
L.HoleMixin,
L.SwapMixin,
L.WatchableMixin);
The CAS function delegate.
L.compareAndSwap = L.meth(L.CAS.prototype.compareAndSwap);
may get pulled out into another lib
Creates a validation function: one that takes
some number of arguments and returns a truthy value
if it "looks good". Checkers also have a message
field useful for building human-readble error messages.
L.checker = function(message, fun) {
var f = function(/* args */) {
return fun.apply(fun, arguments);
};
f['message'] = message;
return f;
};
function condition1(/* checkers */) {
var checkers = _.toArray(arguments);
return function(fun, arg) {
var errors = mapcat(function(isValid) {
return isValid(arg) ? [] : [isValid.message];
}, checkers);
if (!_.isEmpty(errors))
throw new Error(errors.join(", "));
return fun(arg);
};
};
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = L;
}
exports.L = L;
} else {
root.L = L;
}
}).call(this);