137 lines
4.4 KiB
JavaScript
137 lines
4.4 KiB
JavaScript
var assert = require('./assert');
|
|
var isTypeName = require('./isTypeName');
|
|
var FunctionType = require('./Function');
|
|
var isArray = require('./isArray');
|
|
var list = require('./list');
|
|
var isObject = require('./isObject');
|
|
var create = require('./create');
|
|
var isNil = require('./isNil');
|
|
var isBoolean = require('./isBoolean');
|
|
var tuple = require('./tuple');
|
|
var getFunctionName = require('./getFunctionName');
|
|
var getTypeName = require('./getTypeName');
|
|
var isType = require('./isType');
|
|
|
|
function getDefaultName(domain, codomain) {
|
|
return '(' + domain.map(getTypeName).join(', ') + ') => ' + getTypeName(codomain);
|
|
}
|
|
|
|
function isInstrumented(f) {
|
|
return FunctionType.is(f) && isObject(f.instrumentation);
|
|
}
|
|
|
|
function getOptionalArgumentsIndex(types) {
|
|
var end = types.length;
|
|
var areAllMaybes = false;
|
|
for (var i = end - 1; i >= 0; i--) {
|
|
var type = types[i];
|
|
if (!isType(type) || type.meta.kind !== 'maybe') {
|
|
return (i + 1);
|
|
} else {
|
|
areAllMaybes = true;
|
|
}
|
|
}
|
|
return areAllMaybes ? 0 : end;
|
|
}
|
|
|
|
function func(domain, codomain, name) {
|
|
|
|
domain = isArray(domain) ? domain : [domain]; // handle handy syntax for unary functions
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
assert(list(FunctionType).is(domain), function () { return 'Invalid argument domain ' + assert.stringify(domain) + ' supplied to func(domain, codomain, [name]) combinator (expected an array of types)'; });
|
|
assert(FunctionType.is(codomain), function () { return 'Invalid argument codomain ' + assert.stringify(codomain) + ' supplied to func(domain, codomain, [name]) combinator (expected a type)'; });
|
|
assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to func(domain, codomain, [name]) combinator (expected a string)'; });
|
|
}
|
|
|
|
var displayName = name || getDefaultName(domain, codomain);
|
|
var domainLength = domain.length;
|
|
var optionalArgumentsIndex = getOptionalArgumentsIndex(domain);
|
|
|
|
function FuncType(value, path) {
|
|
|
|
if (!isInstrumented(value)) { // automatically instrument the function
|
|
return FuncType.of(value);
|
|
}
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
path = path || [displayName];
|
|
assert(FuncType.is(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/'); });
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
FuncType.meta = {
|
|
kind: 'func',
|
|
domain: domain,
|
|
codomain: codomain,
|
|
name: name,
|
|
identity: true
|
|
};
|
|
|
|
FuncType.displayName = displayName;
|
|
|
|
FuncType.is = function (x) {
|
|
return isInstrumented(x) &&
|
|
x.instrumentation.domain.length === domainLength &&
|
|
x.instrumentation.domain.every(function (type, i) {
|
|
return type === domain[i];
|
|
}) &&
|
|
x.instrumentation.codomain === codomain;
|
|
};
|
|
|
|
FuncType.of = function (f, curried) {
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
assert(FunctionType.is(f), function () { return 'Invalid argument f supplied to func.of ' + displayName + ' (expected a function)'; });
|
|
assert(isNil(curried) || isBoolean(curried), function () { return 'Invalid argument curried ' + assert.stringify(curried) + ' supplied to func.of ' + displayName + ' (expected a boolean)'; });
|
|
}
|
|
|
|
if (FuncType.is(f)) { // makes FuncType.of idempotent
|
|
return f;
|
|
}
|
|
|
|
function fn() {
|
|
var args = Array.prototype.slice.call(arguments);
|
|
var argsLength = args.length;
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
// type-check arguments
|
|
var tupleLength = curried ? argsLength : Math.max(argsLength, optionalArgumentsIndex);
|
|
tuple(domain.slice(0, tupleLength), 'arguments of function ' + displayName)(args);
|
|
}
|
|
|
|
if (curried && argsLength < domainLength) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
assert(argsLength > 0, 'Invalid arguments.length = 0 for curried function ' + displayName);
|
|
}
|
|
var g = Function.prototype.bind.apply(f, [this].concat(args));
|
|
var newDomain = func(domain.slice(argsLength), codomain);
|
|
return newDomain.of(g, true);
|
|
}
|
|
else {
|
|
return create(codomain, f.apply(this, args));
|
|
}
|
|
}
|
|
|
|
fn.instrumentation = {
|
|
domain: domain,
|
|
codomain: codomain,
|
|
f: f
|
|
};
|
|
|
|
fn.displayName = getFunctionName(f);
|
|
|
|
return fn;
|
|
|
|
};
|
|
|
|
return FuncType;
|
|
|
|
}
|
|
|
|
func.getDefaultName = getDefaultName;
|
|
func.getOptionalArgumentsIndex = getOptionalArgumentsIndex;
|
|
module.exports = func;
|