From 1413238228e2c92b625d8534e5a620b2f08df900 Mon Sep 17 00:00:00 2001 From: dsc Date: Sat, 27 Nov 2010 17:01:26 -0800 Subject: [PATCH] Fixes cjs bugs. --- bin/cjs.py | 106 +++-- lib/functional.js | 1050 ----------------------------------------- lib/functional/functional.js | 1050 +++++++++++++++++++++++++++++++++++++++++ lib/functional/to-function.js | 221 +++++++++ src/Y/index.cjs | 4 +- src/Y/tofunction.cjs | 221 --------- 6 files changed, 1346 insertions(+), 1306 deletions(-) delete mode 100644 lib/functional.js create mode 100644 lib/functional/functional.js create mode 100644 lib/functional/to-function.js delete mode 100644 src/Y/tofunction.cjs diff --git a/bin/cjs.py b/bin/cjs.py index 95706e3..250e42d 100755 --- a/bin/cjs.py +++ b/bin/cjs.py @@ -18,19 +18,22 @@ from path import path import pystache -REQUIRE_PAT = re.compile(r'require\(\s*([\'"])(.*?)\1\s*\)') -ROOT = path(__file__).abspath().dirname().dirname() -LIB = ROOT/'lib/cjs' -CWD = path('.') -BLANK = path('') -MODULE_TEMPLATE = '' +LINE_COMMENT_PAT = re.compile(r'(.*?)(//.*?)(?:\n|$)') +PAIR_COMMENT_PAT = re.compile(r'(.*?)(/\*.*?\*/)') +REQUIRE_PAT = re.compile(r'\brequire\(\s*([\'"])(.*?)\1\s*\)') +ROOT = path(__file__).abspath().dirname().dirname() +LIB = ROOT/'lib/cjs' +CWD = path('.') +BLANK = path('') +MODULE_TEMPLATE = '' try: with (LIB/'module.js').open('rU') as f : MODULE_TEMPLATE = f.read() except Exception as ex: print - print 'ROOT={ROOT}, LIB={LIB}, module.js={}'.format(LIB/'module.js', **locals()) + print 'Error reading module template file!' + print ' ROOT={ROOT}, LIB={LIB}, module.js={}'.format(LIB/'module.js', **locals()) raise @@ -69,7 +72,7 @@ def canonicalise(query, base=None): query = trim(query, '/index.cjs', '.cjs', '/index.js', '.js') basedir = path(base or '').dirname() - print "canonicalise(query={query}, basedir={basedir})".format(**locals()) + # print "canonicalise(query={query}, basedir={basedir})".format(**locals()) if query.startswith('.'): id = ( basedir / query ).normpath() @@ -79,10 +82,22 @@ def canonicalise(query, base=None): if id.startswith('..'): raise ResolutionError('Impossible to resolve {} from {}!'.format(query, base)) - print " -->", str(id) + # print " -->", str(id) return str(id) +class Repo(object): + def __init__(self, filepath, url): + self.path = filepath + self.url = url + + def __repr__(self): + return 'Repo({}, url={})'.format(self.path, self.url) + + def __str__(self): + return repr(self) + + class Module(Bunch): DEFAULTS = { @@ -97,7 +112,7 @@ class Module(Bunch): } - def __init__(self, id, file, out=None, compile=True): + def __init__(self, id, file, uri='', out=None, compile=True): self.update(Module.DEFAULTS) self.id = id @@ -109,12 +124,13 @@ class Module(Bunch): else: out = self.file.replace('.cjs', '.js') self.outpath = path(out) + self.uri = uri if compile: self.compile() - print "new!", repr(self) + # print "new!", repr(self) - def compile(self, uri=''): - if uri: self.uri = uri.format(**self) # TODO: calc uri + def compile(self): + # if uri: self.uri = uri.format(**self) # TODO: calc uri if not self.file.endswith('.cjs'): return self @@ -144,10 +160,17 @@ class Module(Bunch): def contents(self): return self.read('contents') + def findRequires(self, text): + text = LINE_COMMENT_PAT.subn(r'\1 ', text)[0] + text = PAIR_COMMENT_PAT.subn(r'\1 ', text)[0] + # print " findRequires() -> %r" % line + for mreq in REQUIRE_PAT.finditer(text): + yield canonicalise(mreq.group(2), self) + @property def requires(self): if self._requires is None: - self._requires = [ canonicalise(m.group(2), self) for m in REQUIRE_PAT.finditer(self.text) ] + self._requires = list(self.findRequires(self.text)) return self._requires def __hash__(self): @@ -172,17 +195,17 @@ class CommonJS(object): """ Compiles JS modules into browser-safe JS files. """ @staticmethod - def discover(files, repos, out=None): + def discover(files, repos, **options): "Discover listed modules and their dependencies." - cjs = CommonJS(repos, out) + cjs = CommonJS(repos, **options) queue = [ cjs.lookup(f) for f in files ] seen = set() while queue: mod = queue.pop(0) seen.add(mod) - print mod, "requirements:", mod.requires + # print mod, "requirements:", mod.requires for query in mod.requires: - print mod, "requires", query + # print mod, "requires", query req = cjs.lookup(query, mod) if req not in seen: queue.append(req) @@ -194,10 +217,14 @@ class CommonJS(object): _graph = None # id -> set(dependants) - def __init__(self, repos, out=None): + def __init__(self, repos, out='build', clean=True): self.modules = {} self.repos = set( path(path(p).abspath()+'/') for p in repos) - self.out = out + self.out = None if out is '' else out + + if self.out is not None and clean: + out = path(self.out) + if out.exists(): out.rmtree() def register(self, id, file): mod = self.modules[id] = Module(id=id, file=file, out=self.out) @@ -206,7 +233,7 @@ class CommonJS(object): def lookup(self, query, base=None): absquery = path(query).abspath() id = canonicalise(query, base) - print "lookup(query={query}, base={base}) -> id={id}".format(**locals()) + # print "lookup(query={query}, base={base}) -> id={id}".format(**locals()) if id in self.modules: return self.modules[id] @@ -223,6 +250,10 @@ class CommonJS(object): raise ResolutionError('Unable to find file for (query={query}, id={id}, base={base}) in repos={}!'.format(map(str, self.repos), **locals())) + def __iter__(self): + for k, mod in self.modules.iteritems(): + yield k, mod + @property def graph(self): if self._graph is None: @@ -259,12 +290,17 @@ def main(): parser = OptionParser( usage = 'usage: %prog [options] file[...] [-- [repo_path[...]]]', - description = 'Resolves imports in JS files', + description = 'Compiles CommonJS modules.', version = '%prog'+" %i.%i.%i" % __version__) parser.add_option("-p", "--repo-paths", dest='repos', default='', - help="Comma-seperated paths to search for unqualified modules. [default: .]") - parser.add_option("-o", "--out", default=None, - help="Root directory to write compiled JS files. [default: Module's directory]") + help="Comma-seperated paths to search for unqualified modules. " + "If a path contains :, the portion afterward will be taken as the repo URL. [default: .]") + parser.add_option("-o", "--out", default='build', + help="Root directory to write compiled JS files. Specify '' to write to module's dir. [default: build]") + parser.add_option("-C", "--no-clean", dest='clean', default=True, action="store_false", + help="Do not clean the out-dir of compiled files. [default: False if -o, True otherwise]") + parser.add_option("-d", "--print-deps", default=False, action="store_true", + help="Prints module dependencies after compiling. [default: %default]") (options, args) = parser.parse_args() @@ -276,15 +312,19 @@ def main(): repos = filter(lambda f: f in args, repos) repos.extend( filter(None, options.repos.split(',')) ) - print 'files:', files, 'repos:', (repos or ['.']) - js = CommonJS.discover(files=files, repos=repos or ['.'], out=options.out) + # print 'files:', files, 'repos:', (repos or ['.']) + js = CommonJS.discover(files=files, repos=repos or ['.'], **options.__dict__) - print 'deplist:' - print js.deplist - print - print 'dependencies:' - print '\n'.join(js.dependencies) - print + if options.print_deps: + column = Popen(['column', '-s', '\t', '-t'], stderr=sys.stderr, stdin=PIPE, stdout=PIPE) + mods = js.modules.values() + for mod in sorted(mods, key=lambda m: len(m.requires), reverse=True): + column.stdin.write('%s\t->\t%r\n' % (mod, sorted(mod.requires))) + # print '%s\t->\t%r' % (mod, sorted(mod.requires)) + + column.stdin.close() + if column.wait() != 0: print 'Some sort of error has occurred!' + print column.stdout.read() return 0 diff --git a/lib/functional.js b/lib/functional.js deleted file mode 100644 index c1742f3..0000000 --- a/lib/functional.js +++ /dev/null @@ -1,1050 +0,0 @@ -/* - * Author: Oliver Steele - * Copyright: Copyright 2007 by Oliver Steele. All rights reserved. - * License: MIT License - * Homepage: http://osteele.com/javascripts/functional - * Source: http://osteele.com/javascripts/functional/functional.js - * Changes: http://osteele.com/javascripts/functional/CHANGES - * Created: 2007-07-11 - * Version: 1.0.2 - * - * - * This file defines some higher-order methods and functions for - * functional and function-level programming. - */ - -/// `Functional` is the namespace for higher-order functions. -var Functional = this.Functional || {}; - -/** - * This function copies all the public functions in `Functional` except itself - * into the global namespace. If the optional argument $except$ is present, - * functions named by its property names are not copied. - * >> Functional.install() - */ -Functional.install = function(except) { - var source = Functional, - target = (function() { return this; })(); // References the global object. - for (var name in source) - name == 'install' - || name.charAt(0) == '_' - || except && name in except - || !source.hasOwnProperty(name) // work around Prototype - || (target[name] = source[name]); -} - -/// ^ Higher-order functions - -/** - * Returns a function that applies the last argument of this - * function to its input, and the penultimate argument to the - * result of the application, and so on. - * == compose(f1, f2, f3..., fn)(args) == f1(f2(f3(...(fn(args...))))) - * :: (a2 -> a1) (a3 -> a2)... (a... -> a_{n}) -> a... -> a1 - * >> compose('1+', '2*')(2) -> 5 - */ -Functional.compose = function(/*fn...*/) { - var fns = Functional.map(Function.toFunction, arguments), - arglen = fns.length; - return function() { - for (var i = arglen; --i >= 0; ) - arguments = [fns[i].apply(this, arguments)]; - return arguments[0]; - } -} - -/** - * Same as `compose`, except applies the functions in argument-list order. - * == sequence(f1, f2, f3..., fn)(args...) == fn(...(f3(f2(f1(args...))))) - * :: (a... -> a1) (a1 -> a2) (a2 -> a3)... (a_{n-1} -> a_{n}) -> a... -> a_{n} - * >> sequence('1+', '2*')(2) -> 6 - */ -Functional.sequence = function(/*fn...*/) { - var fns = Functional.map(Function.toFunction, arguments), - arglen = fns.length; - return function() { - for (var i = 0; i < arglen; i++) - arguments = [fns[i].apply(this, arguments)]; - return arguments[0]; - } -} - -/** - * Applies `fn` to each element of `sequence`. - * == map(f, [x1, x2...]) = [f(x, 0), f(x2, 1), ...] - * :: (a ix -> boolean) [a] -> [a] - * >> map('1+', [1,2,3]) -> [2, 3, 4] - * - * If `object` is supplied, it is the object of the call. - * - * The fusion rule: - * >> map('+1', map('*2', [1,2,3])) -> [3, 5, 7] - * >> map(compose('+1', '*2'), [1,2,3]) -> [3, 5, 7] - */ -Functional.map = function(fn, sequence, object) { - fn = Function.toFunction(fn); - var len = sequence.length, - result = new Array(len); - for (var i = 0; i < len; i++) - result[i] = fn.apply(object, [sequence[i], i]); - return result; -} - -/** - * Applies `fn` to `init` and the first element of `sequence`, - * and then to the result and the second element, and so on. - * == reduce(f, init, [x0, x1, x2]) == f(f(f(init, x0), x1), x2) - * :: (a b -> a) a [b] -> a - * >> reduce('x y -> 2*x+y', 0, [1,0,1,0]) -> 10 - */ -Functional.reduce = function(fn, init, sequence, object) { - fn = Function.toFunction(fn); - var len = sequence.length, - result = init; - for (var i = 0; i < len; i++) - result = fn.apply(object, [result, sequence[i]]); - return result; -} - -/** - * Returns a list of those elements $x$ of `sequence` such that - * $fn(x)$ returns true. - * :: (a -> boolean) [a] -> [a] - * >> select('%2', [1,2,3,4]) -> [1, 3] - */ -Functional.select = function(fn, sequence, object) { - fn = Function.toFunction(fn); - var len = sequence.length, - result = []; - for (var i = 0; i < len; i++) { - var x = sequence[i]; - fn.apply(object, [x, i]) && result.push(x); - } - return result; -} - -/// A synonym for `select`. -Functional.filter = Functional.select; - -/// A synonym for `reduce`. -Functional.foldl = Functional.reduce; - -/** - * Same as `foldl`, but applies the function from right to left. - * == foldr(f, init, [x0, x1, x2]) == fn(x0, f(x1, f(x2, init))) - * :: (a b -> b) b [a] -> b - * >> foldr('x y -> 2*x+y', 100, [1,0,1,0]) -> 104 - */ -Functional.foldr = function(fn, init, sequence, object) { - fn = Function.toFunction(fn); - var len = sequence.length, - result = init; - for (var i = len; --i >= 0; ) - result = fn.apply(object, [sequence[i], result]); - return result; -} - -/// ^^ Predicates - -/** - * Returns a function that returns `true` when all the arguments, applied - * to the returned function's arguments, returns true. - * == and(f1, f2...)(args...) == f1(args...) && f2(args...)... - * :: [a -> boolean] a -> a - * >> and('>1', '>2')(2) -> false - * >> and('>1', '>2')(3) -> true - * >> and('>1', 'error()')(1) -> false - */ -Functional.and = function(/*functions...*/) { - var args = Functional.map(Function.toFunction, arguments), - arglen = args.length; - return function() { - var value = true; - for (var i = 0; i < arglen; i++) - if (!(value = args[i].apply(this, arguments))) - break; - return value; - } -} - -/** - * Returns a function that returns `true` when any argument, applied - * to the returned function's arguments, returns true. - * == or(f1, f2...)(args...) == f1(args...) || f2(args...)... - * :: [a -> boolean] a -> a - * >> or('>1', '>2')(1) -> false - * >> or('>1', '>2')(2) -> true - * >> or('>1', 'error()')(2) -> true - */ -Functional.or = function(/*functions...*/) { - var args = Functional.map(Function.toFunction, arguments), - arglen = args.length; - return function() { - var value = false; - for (var i = 0; i < arglen; i++) - if ((value = args[i].apply(this, arguments))) - break; - return value; - } -} - -/** - * Returns true when $fn(x)$ returns true for some element $x$ of - * `sequence`. The returned function short-circuits. - * == some(f, [x1, x2, x3, ...]) == f(x1) || f(x2) || f(x3)... - * :: (a -> boolean) [a] -> boolean - * >> some('>2', [1,2,3]) -> true - * >> some('>10', [1,2,3]) -> false - */ -Functional.some = function(fn, sequence, object) { - fn = Function.toFunction(fn); - var len = sequence.length, - value = false; - for (var i = 0; i < len; i++) - if ((value = fn.call(object, sequence[i]))) - break; - return value; -} - -/** - * Returns true when $fn(x)$ returns true for every element $x$ of - * `sequence`. The returned function short-circuits. - * == every(f, [x1, x2, x3, ...]) == f(x1) && f(x2) && f(x3)... - * :: (a -> boolean) [a] -> boolean - * >> every('<2', [1,2,3]) -> false - * >> every('<10', [1,2,3]) -> true - */ -Functional.every = function(fn, sequence, object) { - fn = Function.toFunction(fn); - var len = sequence.length, - value = true; - for (var i = 0; i < len; i++) - if (!(value = fn.call(object, sequence[i]))) - break; - return value; -} - -/** - * Returns a function that returns `true` when $fn()$ returns false. - * == f.not()(args...) == !f(args...) - * :: (a -> boolean) -> (a -> boolean) - * >> not(Functional.K(true))() -> false - * >> not(Functional.K(false))() -> true - */ -Functional.not = function(fn) { - fn = Function.toFunction(fn); - return function() { - return !fn.apply(null, arguments); - } -} - -/** - * Returns a function that returns true when this function's arguments - * applied to that functions are always the same. The returned function - * short-circuits. - * == equal(f1, f2...)(args...) == f1(args...) == f2(args...)... - * :: [a... -> b] -> a... -> b - * >> equal()() -> true - * >> equal(K(1))() -> true - * >> equal(K(1), K(1))() -> true - * >> equal(K(1), K(2))() -> false - * >> equal(K(1), K(2), 'error()')() -> false - */ -Functional.equal = function(/*fn...*/) { - var arglen = arguments.length, - args = Functional.map(Function.toFunction, arguments); - if (!arglen) return Functional.K(true); - // if arglen == 1 it's also constant true, but - // call it for effect. - return function() { - var value = args[0].apply(this, arguments); - for (var i = 1; i < arglen; i++) - if (value != args[i].apply(this, args)) - return false; - return true; - } -} - - -/// ^^ Utilities - -/** - * Returns its argument coerced to a function. - * >> lambda('1+')(2) -> 3 - * >> lambda(function(n){return n+1})(2) -> 3 - */ -Functional.lambda = function(object) { - return object.toFunction(); -} - - -/** - * Returns a function that takes an object as an argument, and applies - * `object`'s `methodName` method to `arguments`. - * == invoke(name)(object, args...) == object[name](args...) - * :: name args... -> object args2... -> object[name](args... args2...) - * >> invoke('toString')(123) -> "123" - */ -Functional.invoke = function(methodName/*, arguments*/) { - var args = Array.slice(arguments, 1); - return function(object) { - return object[methodName].apply(object, Array.slice(arguments, 1).concat(args)); - } -} - -/** - * Returns a function that takes an object, and returns the value of its - * `name` property. `pluck(name)` is equivalent to `'_.name'.lambda()`. - * == pluck(name)(object) == object[name] - * :: name -> object -> object[name] - * >> pluck('length')("abc") -> 3 - */ -Functional.pluck = function(name) { - return function(object) { - return object[name]; - } -} - -/** - * Returns a function that, while $pred(value)$ is true, applies `fn` to - * $value$ to produce a new value, which is used as an input for the next round. - * The returned function returns the first $value$ for which $pred(value)$ - * is false. - * :: (a -> boolean) (a -> a) -> a - * >> until('>10', '2*')(1) -> 16 - */ -Functional.until = function(pred, fn) { - fn = Function.toFunction(fn); - pred = Function.toFunction(pred); - return function(value) { - while (!pred.call(null, value)) - value = fn.call(null, value); - return value; - } -} - -/** - * :: [a] [b]... -> [[a b]...] - * == zip(a, b...) == [[a0, b0], [a1, b1], ...] - * Did you know that `zip` can transpose a matrix? - * >> zip.apply(null, [[1,2],[3,4]]) -> [[1, 3], [2, 4]] - */ -Functional.zip = function(/*args...*/) { - var n = Math.min.apply(null, Functional.map('.length', arguments)); - var results = new Array(n); - for (var i = 0; i < n; i++) { - var key = String(i); - results[key] = Functional.map(pluck(key), arguments); - }; - return results; -} - -Functional._startRecordingMethodChanges = function(object) { - var initialMethods = {}; - for (var name in object) - initialMethods[name] = object[name]; - return {getChangedMethods: function() { - var changedMethods = {}; - for (var name in object) - if (object[name] != initialMethods[name]) - changedMethods[name] = object[name]; - return changedMethods; - }}; -} - -// For each method that this file defined on `Function.prototype`, -// define a function on `Functional` that delegates to it. -Functional._attachMethodDelegates = function(methods) { - for (var name in methods) - Functional[name] = Functional[name] || (function(name) { - var fn = methods[name]; - return function(object) { - return fn.apply(Function.toFunction(object), Array.slice(arguments, 1)); - } - })(name); -} - -// Record the current contents of `Function.prototype`, so that we -// can see what we've added later. -Functional.__initalFunctionState = Functional._startRecordingMethodChanges(Function.prototype); - -/// ^ Higher-order methods - -/// ^^ Partial function application - -/** - * Returns a bound method on `object`, optionally currying `args`. - * == f.bind(obj, args...)(args2...) == f.apply(obj, [args..., args2...]) - */ -Function.prototype.bind = function(object/*, args...*/) { - var fn = this; - var args = Array.slice(arguments, 1); - return function() { - return fn.apply(object, args.concat(Array.slice(arguments, 0))); - } -} - -/** - * Returns a function that applies the underlying function to `args`, and - * ignores its own arguments. - * :: (a... -> b) a... -> (... -> b) - * == f.saturate(args...)(args2...) == f(args...) - * >> Math.max.curry(1, 2)(3, 4) -> 4 - * >> Math.max.saturate(1, 2)(3, 4) -> 2 - * >> Math.max.curry(1, 2).saturate()(3, 4) -> 2 - */ -Function.prototype.saturate = function(/*args*/) { - var fn = this; - var args = Array.slice(arguments, 0); - return function() { - return fn.apply(this, args); - } -} - -/** - * Invoking the function returned by this function only passes `n` - * arguments to the underlying function. If the underlying function - * is not saturated, the result is a function that passes all its - * arguments to the underlying function. (That is, `aritize` only - * affects its immediate caller, and not subsequent calls.) - * >> '[a,b]'.lambda()(1,2) -> [1, 2] - * >> '[a,b]'.lambda().aritize(1)(1,2) -> [1, undefined] - * >> '+'.lambda()(1,2)(3) -> error - * >> '+'.lambda().ncurry(2).aritize(1)(1,2)(3) -> 4 - * - * `aritize` is useful to remove optional arguments from a function that - * is passed to a higher-order function that supplies *different* optional - * arguments. - * - * For example, many implementations of `map` and other collection - * functions, including those in this library, call the function argument - * with both the collection element - * and its position. This is convenient when expected, but can wreak - * havoc when the function argument is a curried function that expects - * a single argument from `map` and the remaining arguments from when - * the result of `map` is applied. - */ -Function.prototype.aritize = function(n) { - var fn = this; - return function() { - return fn.apply(this, Array.slice(arguments, 0, n)); - } -} - -/** - * Returns a function that, applied to an argument list $arg2$, - * applies the underlying function to $args ++ arg2$. - * :: (a... b... -> c) a... -> (b... -> c) - * == f.curry(args1...)(args2...) == f(args1..., args2...) - * - * Note that, unlike in languages with true partial application such as Haskell, - * `curry` and `uncurry` are not inverses. This is a repercussion of the - * fact that in JavaScript, unlike Haskell, a fully saturated function is - * not equivalent to the value that it returns. The definition of `curry` - * here matches semantics that most people have used when implementing curry - * for procedural languages. - * - * This implementation is adapted from - * [http://www.coryhudson.com/blog/2007/03/10/javascript-currying-redux/]. - */ -Function.prototype.curry = function(/*args...*/) { - var fn = this; - var args = Array.slice(arguments, 0); - return function() { - return fn.apply(this, args.concat(Array.slice(arguments, 0))); - }; -} - -/* - * Right curry. Returns a function that, applied to an argument list $args2$, - * applies the underlying function to $args2 + args$. - * == f.curry(args1...)(args2...) == f(args2..., args1...) - * :: (a... b... -> c) b... -> (a... -> c) - */ -Function.prototype.rcurry = function(/*args...*/) { - var fn = this; - var args = Array.slice(arguments, 0); - return function() { - return fn.apply(this, Array.slice(arguments, 0).concat(args)); - }; -} - -/** - * Same as `curry`, except only applies the function when all - * `n` arguments are saturated. - */ -Function.prototype.ncurry = function(n/*, args...*/) { - var fn = this; - var largs = Array.slice(arguments, 1); - return function() { - var args = largs.concat(Array.slice(arguments, 0)); - if (args.length < n) { - args.unshift(n); - return fn.ncurry.apply(fn, args); - } - return fn.apply(this, args); - }; -} - -/** - * Same as `rcurry`, except only applies the function when all - * `n` arguments are saturated. - */ -Function.prototype.rncurry = function(n/*, args...*/) { - var fn = this; - var rargs = Array.slice(arguments, 1); - return function() { - var args = Array.slice(arguments, 0).concat(rargs); - if (args.length < n) { - args.unshift(n); - return fn.rncurry.apply(fn, args); - } - return fn.apply(this, args); - }; -} - -/** - * `_` (underscore) is bound to a unique value for use in `partial`, below. - * This is a global variable, but it's also a property of `Function` in case - * you overwrite or bind over the global one. - */ -_ = Function._ = {}; - -/** - * Returns a function $f$ such that $f(args2)$ is equivalent to - * the underlying function applied to a combination of $args$ and $args2$. - * - * `args` is a partially-specified argument: it's a list with "holes", - * specified by the special value `_`. It is combined with $args2$ as - * follows: - * - * From left to right, each value in $args2$ fills in the leftmost - * remaining hole in `args`. Any remaining values - * in $args2$ are appended to the result of the filling-in process - * to produce the combined argument list. - * - * If the combined argument list contains any occurrences of `_`, the result - * of the application of $f$ is another partial function. Otherwise, the - * result is the same as the result of applying the underlying function to - * the combined argument list. - */ -Function.prototype.partial = function(/*args*/) { - var fn = this; - var _ = Function._; - var args = Array.slice(arguments, 0); - //substitution positions - var subpos = [], value; - for (var i = 0; i < arguments.length; i++) - arguments[i] == _ && subpos.push(i); - return function() { - var specialized = args.concat(Array.slice(arguments, subpos.length)); - for (var i = 0; i < Math.min(subpos.length, arguments.length); i++) - specialized[subpos[i]] = arguments[i]; - for (var i = 0; i < specialized.length; i++) - if (specialized[i] == _) - return fn.partial.apply(fn, specialized); - return fn.apply(this, specialized); - } -} - -/// ^^ Combinators - -/// ^^^ Combinator Functions - -/** - * The identity function: $x -> x$. - * == I(x) == x - * == I == 'x'.lambda() - * :: a -> a - * >> Functional.I(1) -> 1 - */ -Functional.I = function(x) {return x}; - -/** - * Returns a constant function that returns `x`. - * == K(x)(y) == x - * :: a -> b -> a - * >> Functional.K(1)(2) -> 1 - */ -Functional.K = function(x) {return function() {return x}}; - -/// A synonym for `Functional.I` -Functional.id = Functional.I; - -/// A synonym for `Functional.K` -Functional.constfn = Functional.K; - - -/** - * Returns a function that applies the first function to the - * result of the second, but passes all its arguments too. - * == S(f, g)(args...) == f(g(args...), args...) - * - * This is useful for composing functions when each needs access - * to the arguments to the composed function. For example, - * the following function multiples its last two arguments, - * and adds the first to that. - * >> Function.S('+', '_ a b -> a*b')(2,3,4) -> 14 - * - * Curry this to get a version that takes its arguments in - * separate calls: - * >> Function.S.curry('+')('_ a b -> a*b')(2,3,4) -> 14 - */ -Function.S = function(f, g) { - f = Function.toFunction(f); - g = Function.toFunction(g); - return function() { - return f.apply(this, [g.apply(this, arguments)].concat(Array.slice(arguments, 0))); - } -} - -/// ^^^ Combinator methods - -/** - * Returns a function that swaps its first two arguments before - * passing them to the underlying function. - * == f.flip()(a, b, c...) == f(b, a, c...) - * :: (a b c...) -> (b a c...) - * >> ('a/b'.lambda()).flip()(1,2) -> 2 - * - * For more general derangements, you can also use `prefilterSlice` - * with a string lambda: - * >> '100*a+10*b+c'.lambda().prefilterSlice('a b c -> [b, c, a]')(1,2,3) -> 231 - */ -Function.prototype.flip = function() { - var fn = this; - return function() { - var args = Array.slice(arguments, 0); - args = args.slice(1,2).concat(args.slice(0,1)).concat(args.slice(2)); - return fn.apply(this, args); - } -} - -/** - * Returns a function that applies the underlying function to its - * first argument, and the result of that application to the remaining - * arguments. - * == f.uncurry(a, b...) == f(a)(b...) - * :: (a -> b -> c) -> (a, b) -> c - * >> 'a -> b -> a/b'.lambda().uncurry()(1,2) -> 0.5 - * - * Note that `uncurry` is *not* the inverse of `curry`. - */ -Function.prototype.uncurry = function() { - var fn = this; - return function() { - var f1 = fn.apply(this, Array.slice(arguments, 0, 1)); - return f1.apply(this, Array.slice(arguments, 1)); - } -} - -/** - * ^^ Filtering - * - * Filters intercept a value before it is passed to a function, and apply the - * underlying function to the modified value. - */ - -/** - * `prefilterObject` returns a function that applies the underlying function - * to the same arguments, but to an object that is the result of appyling - * `filter` to the invocation object. - * == fn.prefilterObject(filter).apply(object, args...) == fn.apply(filter(object), args...) - * == fn.bind(object) == compose(fn.prefilterObject, Functional.K(object)) - * >> 'this'.lambda().prefilterObject('n+1').apply(1) -> 2 - */ -Function.prototype.prefilterObject = function(filter) { - filter = Function.toFunction(filter); - var fn = this; - return function() { - return fn.apply(filter(this), arguments); - } -} - -/** - * `prefilterAt` returns a function that applies the underlying function - * to a copy of the arguments, where the `index`th argument has been - * replaced by the value of `filter(argument[index])`. - * == fn.prefilterAt(i, filter)(a1, a2, ..., a_{n}) == fn(a1, a2, ..., filter(a_{i}), ..., a_{n}) - * >> '[a,b,c]'.lambda().prefilterAt(1, '2*')(2,3,4) -> [2, 6, 4] - */ -Function.prototype.prefilterAt = function(index, filter) { - filter = Function.toFunction(filter); - var fn = this; - return function() { - var args = Array.slice(arguments, 0); - args[index] = filter.call(this, args[index]); - return fn.apply(this, args); - } -} - -/** - * `prefilterSlice` returns a function that applies the underlying function - * to a copy of the arguments, where the arguments `start` through - * `end` have been replaced by the value of `filter(argument.slice(start,end))`, - * which must return a list. - * == fn.prefilterSlice(i0, i1, filter)(a1, a2, ..., a_{n}) == fn(a1, a2, ..., filter(args_{i0}, ..., args_{i1}), ..., a_{n}) - * >> '[a,b,c]'.lambda().prefilterSlice('[a+b]', 1, 3)(1,2,3,4) -> [1, 5, 4] - * >> '[a,b]'.lambda().prefilterSlice('[a+b]', 1)(1,2,3) -> [1, 5] - * >> '[a]'.lambda().prefilterSlice(compose('[_]', Math.max))(1,2,3) -> [3] - */ -Function.prototype.prefilterSlice = function(filter, start, end) { - filter = Function.toFunction(filter); - start = start || 0; - var fn = this; - return function() { - var args = Array.slice(arguments, 0); - var e = end < 0 ? args.length + end : end || args.length; - args.splice.apply(args, [start, (e||args.length)-start].concat(filter.apply(this, args.slice(start, e)))); - return fn.apply(this, args); - } -} - -/// ^^ Method Composition - -/** - * `compose` returns a function that applies the underlying function - * to the result of the application of `fn`. - * == f.compose(g)(args...) == f(g(args...)) - * >> '1+'.lambda().compose('2*')(3) -> 7 - * - * Note that, unlike `Functional.compose`, the `compose` method on - * function only takes a single argument. - * == Functional.compose(f, g) == f.compose(g) - * == Functional.compose(f, g, h) == f.compose(g).compose(h) - */ -Function.prototype.compose = function(fn) { - var self = this; - fn = Function.toFunction(fn); - return function() { - return self.apply(this, [fn.apply(this, arguments)]); - } -} - -/** - * `sequence` returns a function that applies the underlying function - * to the result of the application of `fn`. - * == f.sequence(g)(args...) == g(f(args...)) - * == f.sequence(g) == g.compose(f) - * >> '1+'.lambda().sequence('2*')(3) -> 8 - * - * Note that, unlike `Functional.compose`, the `sequence` method on - * function only takes a single argument. - * == Functional.sequence(f, g) == f.sequence(g) - * == Functional.sequence(f, g, h) == f.sequence(g).sequence(h) - */ -Function.prototype.sequence = function(fn) { - var self = this; - fn = Function.toFunction(fn); - return function() { - return fn.apply(this, [self.apply(this, arguments)]); - } -} - -/** - * Returns a function that is equivalent to the underlying function when - * `guard` returns true, and otherwise is equivalent to the application - * of `otherwise` to the same arguments. - * - * `guard` and `otherwise` default to `Functional.I`. `guard` with - * no arguments therefore returns a function that applies the - * underlying function to its value only if the value is true, - * and returns the value otherwise. - * == f.guard(g, h)(args...) == f(args...), when g(args...) is true - * == f.guard(g ,h)(args...) == h(args...), when g(args...) is false - * >> '[_]'.lambda().guard()(1) -> [1] - * >> '[_]'.lambda().guard()(null) -> null - * >> '[_]'.lambda().guard(null, Functional.K('n/a'))(null) -> "n/a" - * >> 'x+1'.lambda().guard('<10', Functional.K(null))(1) -> 2 - * >> 'x+1'.lambda().guard('<10', Functional.K(null))(10) -> null - * >> '/'.lambda().guard('p q -> q', Functional.K('n/a'))(1, 2) -> 0.5 - * >> '/'.lambda().guard('p q -> q', Functional.K('n/a'))(1, 0) -> "n/a" - * >> '/'.lambda().guard('p q -> q', '-> "n/a"')(1, 0) -> "n/a" - */ -Function.prototype.guard = function(guard, otherwise) { - var fn = this; - guard = Function.toFunction(guard || Functional.I); - otherwise = Function.toFunction(otherwise || Functional.I); - return function() { - return (guard.apply(this, arguments) ? fn : otherwise).apply(this, arguments); - } -} - -/// ^^ Utilities - -/** - * Returns a function identical to this function except that - * it prints its arguments on entry and its return value on exit. - * This is useful for debugging function-level programs. - */ -Function.prototype.traced = function(name) { - var self = this, - global = (function() { return this; })(), - log = function() {}; - - if (typeof console != 'undefined' && typeof console.info == 'function') { - log = console.info; - } else if (typeof print == 'function') { - log = print; - } - - name = name || self; - return function() { - log('[', name, 'apply(', this!=global && this, ',', arguments, ')'); - var result = self.apply(this, arguments); - log(']', name, ' -> ', result); - return result; - } -} - - -/** - * ^^ Function methods as functions - * - * In addition to the functions defined above, every method defined - * on `Function` is also available as a function in `Functional`, that - * coerces its first argument to a `Function` and applies - * the remaining arguments to this. - * - * A few examples make this clearer: - * == curry(fn, args...) == fn.curry(args...) - * >> Functional.flip('a/b')(1, 2) -> 2 - * >> Functional.curry('a/b', 1)(2) -> 0.5 - - * For each method that this file defined on Function.prototype, - * define a function on Functional that delegates to it. - */ -Functional._attachMethodDelegates(Functional.__initalFunctionState.getChangedMethods()); -delete Functional.__initalFunctionState; - - -// In case to-function.js isn't loaded. -Function.toFunction = Function.toFunction || Functional.K; - -if (!Array.slice) { // mozilla already supports this - Array.slice = (function(slice) { - return function(object) { - return slice.apply(object, slice.call(arguments, 1)); - }; - })(Array.prototype.slice); -} -/* - * Author: Oliver Steele - * Copyright: Copyright 2007 by Oliver Steele. All rights reserved. - * License: MIT License - * Homepage: http://osteele.com/javascripts/functional - * Created: 2007-07-11 - * Version: 1.0.2 - * - * - * This defines "string lambdas", that allow strings such as `x+1` and - * `x -> x+1` to be used in some contexts as functions. - */ - - -/// ^ String lambdas - -/** - * Turns a string that contains a JavaScript expression into a - * `Function` that returns the value of that expression. - * - * If the string contains a `->`, this separates the parameters from the body: - * >> 'x -> x + 1'.lambda()(1) -> 2 - * >> 'x y -> x + 2*y'.lambda()(1, 2) -> 5 - * >> 'x, y -> x + 2*y'.lambda()(1, 2) -> 5 - * - * Otherwise, if the string contains a `_`, this is the parameter: - * >> '_ + 1'.lambda()(1) -> 2 - * - * Otherwise if the string begins or ends with an operator or relation, - * prepend or append a parameter. (The documentation refers to this type - * of string as a "section".) - * >> '/2'.lambda()(4) -> 2 - * >> '2/'.lambda()(4) -> 0.5 - * >> '/'.lambda()(2,4) -> 0.5 - * Sections can end, but not begin with, `-`. (This is to avoid interpreting - * e.g. `-2*x` as a section). On the other hand, a string that either begins - * or ends with `/` is a section, so an expression that begins or ends with a - * regular expression literal needs an explicit parameter. - * - * Otherwise, each variable name is an implicit parameter: - * >> 'x + 1'.lambda()(1) -> 2 - * >> 'x + 2*y'.lambda()(1, 2) -> 5 - * >> 'y + 2*x'.lambda()(1, 2) -> 5 - * - * Implicit parameter detection ignores strings literals, variable names that - * start with capitals, and identifiers that precede `:` or follow `.`: - * >> map('"im"+root', ["probable", "possible"]) -> ["improbable", "impossible"] - * >> 'Math.cos(angle)'.lambda()(Math.PI) -> -1 - * >> 'point.x'.lambda()({x:1, y:2}) -> 1 - * >> '({x:1, y:2})[key]'.lambda()('x') -> 1 - * - * Implicit parameter detection mistakenly looks inside regular expression - * literals for variable names. It also doesn't know to ignore JavaScript - * keywords and bound variables. (The only way you can get these last two is - * with a function literal inside the string. This is outside the intended use - * case for string lambdas.) - * - * Use `_` (to define a unary function) or `->`, if the string contains anything - * that looks like a free variable but shouldn't be used as a parameter, or - * to specify parameters that are ordered differently from their first - * occurrence in the string. - * - * Chain `->`s to create a function in uncurried form: - * >> 'x -> y -> x + 2*y'.lambda()(1)(2) -> 5 - * >> 'x -> y -> z -> x + 2*y+3*z'.lambda()(1)(2)(3) -> 14 - * - * `this` and `arguments` are special: - * >> 'this'.call(1) -> 1 - * >> '[].slice.call(arguments, 0)'.call(null,1,2) -> [1, 2] - */ -String.prototype.lambda = function() { - var params = [], - expr = this, - sections = expr.ECMAsplit(/\s*->\s*/m); - if (sections.length > 1) { - while (sections.length) { - expr = sections.pop(); - params = sections.pop().split(/\s*,\s*|\s+/m); - sections.length && sections.push('(function('+params+'){return ('+expr+')})'); - } - } else if (expr.match(/\b_\b/)) { - params = '_'; - } else { - // test whether an operator appears on the left (or right), respectively - var leftSection = expr.match(/^\s*(?:[+*\/%&|\^\.=<>]|!=)/m), - rightSection = expr.match(/[+\-*\/%&|\^\.=<>!]\s*$/m); - if (leftSection || rightSection) { - if (leftSection) { - params.push('$1'); - expr = '$1' + expr; - } - if (rightSection) { - params.push('$2'); - expr = expr + '$2'; - } - } else { - // `replace` removes symbols that are capitalized, follow '.', - // precede ':', are 'this' or 'arguments'; and also the insides of - // strings (by a crude test). `match` extracts the remaining - // symbols. - var vars = this.replace(/(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*|[a-zA-Z_$][a-zA-Z_$\d]*\s*:|this|arguments|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, '').match(/([a-z_$][a-z_$\d]*)/gi) || []; // ' - for (var i = 0, v; v = vars[i++]; ) - params.indexOf(v) >= 0 || params.push(v); - } - } - return new Function(params, 'return (' + expr + ')'); -} - -/// Turn on caching for `string` -> `Function` conversion. -String.prototype.lambda.cache = function() { - var proto = String.prototype, - cache = {}, - uncached = proto.lambda, - cached = function() { - var key = '#' + this; // avoid hidden properties on Object.prototype - return cache[key] || (cache[key] = uncached.call(this)); - }; - cached.cached = function(){}; - cached.uncache = function(){proto.lambda = uncached}; - proto.lambda = cached; -} - -/** - * ^^ Duck-Typing - * - * Strings support `call` and `apply`. This duck-types them as - * functions, to some callers. - */ - -/** - * Coerce the string to a function and then apply it. - * >> 'x+1'.apply(null, [2]) -> 3 - * >> '/'.apply(null, [2, 4]) -> 0.5 - */ -String.prototype.apply = function(thisArg, args) { - return this.toFunction().apply(thisArg, args); -} - -/** - * Coerce the string to a function and then call it. - * >> 'x+1'.call(null, 2) -> 3 - * >> '/'.call(null, 2, 4) -> 0.5 - */ -String.prototype.call = function() { - return this.toFunction().apply(arguments[0], - Array.prototype.slice.call(arguments, 1)); -} - -/// ^^ Coercion - -/** - * Returns a `Function` that perfoms the action described by this - * string. If the string contains a `return`, applies - * `new Function` to it. Otherwise, this function returns - * the result of `this.lambda()`. - * >> '+1'.toFunction()(2) -> 3 - * >> 'return 1'.toFunction()(1) -> 1 - */ -String.prototype.toFunction = function() { - var body = this; - if (body.match(/\breturn\b/)) - return new Function(this); - return this.lambda(); -} - -/** - * Returns this function. `Function.toFunction` calls this. - * >> '+1'.lambda().toFunction()(2) -> 3 - */ -Function.prototype.toFunction = function() { - return this; -} - -/** - * Coerces `fn` into a function if it is not already one, - * by calling its `toFunction` method. - * >> Function.toFunction(function() {return 1})() -> 1 - * >> Function.toFunction('+1')(2) -> 3 - * - * `Function.toFunction` requires an argument that can be - * coerced to a function. A nullary version can be - * constructed via `guard`: - * >> Function.toFunction.guard()('1+') -> function() - * >> Function.toFunction.guard()(null) -> null - * - * `Function.toFunction` doesn't coerce arbitrary values to functions. - * It might seem convenient to treat - * `Function.toFunction(value)` as though it were the - * constant function that returned `value`, but it's rarely - * useful and it hides errors. Use `Functional.K(value)` instead, - * or a lambda string when the value is a compile-time literal: - * >> Functional.K('a string')() -> "a string" - * >> Function.toFunction('"a string"')() -> "a string" - */ -Function.toFunction = function(value) { - return value.toFunction(); -} - -// Utilities - -// IE6 split is not ECMAScript-compliant. This breaks '->1'.lambda(). -// ECMAsplit is an ECMAScript-compliant `split`, although only for -// one argument. -String.prototype.ECMAsplit = - // The test is from the ECMAScript reference. - ('ab'.split(/a*/).length > 1 - ? String.prototype.split - : function(separator, limit) { - if (typeof limit != 'undefined') - throw "ECMAsplit: limit is unimplemented"; - var result = this.split.apply(this, arguments), - re = RegExp(separator), - savedIndex = re.lastIndex, - match = re.exec(this); - if (match && match.index == 0) - result.unshift(''); - // in case `separator` was already a RegExp: - re.lastIndex = savedIndex; - return result; - }); diff --git a/lib/functional/functional.js b/lib/functional/functional.js new file mode 100644 index 0000000..c1742f3 --- /dev/null +++ b/lib/functional/functional.js @@ -0,0 +1,1050 @@ +/* + * Author: Oliver Steele + * Copyright: Copyright 2007 by Oliver Steele. All rights reserved. + * License: MIT License + * Homepage: http://osteele.com/javascripts/functional + * Source: http://osteele.com/javascripts/functional/functional.js + * Changes: http://osteele.com/javascripts/functional/CHANGES + * Created: 2007-07-11 + * Version: 1.0.2 + * + * + * This file defines some higher-order methods and functions for + * functional and function-level programming. + */ + +/// `Functional` is the namespace for higher-order functions. +var Functional = this.Functional || {}; + +/** + * This function copies all the public functions in `Functional` except itself + * into the global namespace. If the optional argument $except$ is present, + * functions named by its property names are not copied. + * >> Functional.install() + */ +Functional.install = function(except) { + var source = Functional, + target = (function() { return this; })(); // References the global object. + for (var name in source) + name == 'install' + || name.charAt(0) == '_' + || except && name in except + || !source.hasOwnProperty(name) // work around Prototype + || (target[name] = source[name]); +} + +/// ^ Higher-order functions + +/** + * Returns a function that applies the last argument of this + * function to its input, and the penultimate argument to the + * result of the application, and so on. + * == compose(f1, f2, f3..., fn)(args) == f1(f2(f3(...(fn(args...))))) + * :: (a2 -> a1) (a3 -> a2)... (a... -> a_{n}) -> a... -> a1 + * >> compose('1+', '2*')(2) -> 5 + */ +Functional.compose = function(/*fn...*/) { + var fns = Functional.map(Function.toFunction, arguments), + arglen = fns.length; + return function() { + for (var i = arglen; --i >= 0; ) + arguments = [fns[i].apply(this, arguments)]; + return arguments[0]; + } +} + +/** + * Same as `compose`, except applies the functions in argument-list order. + * == sequence(f1, f2, f3..., fn)(args...) == fn(...(f3(f2(f1(args...))))) + * :: (a... -> a1) (a1 -> a2) (a2 -> a3)... (a_{n-1} -> a_{n}) -> a... -> a_{n} + * >> sequence('1+', '2*')(2) -> 6 + */ +Functional.sequence = function(/*fn...*/) { + var fns = Functional.map(Function.toFunction, arguments), + arglen = fns.length; + return function() { + for (var i = 0; i < arglen; i++) + arguments = [fns[i].apply(this, arguments)]; + return arguments[0]; + } +} + +/** + * Applies `fn` to each element of `sequence`. + * == map(f, [x1, x2...]) = [f(x, 0), f(x2, 1), ...] + * :: (a ix -> boolean) [a] -> [a] + * >> map('1+', [1,2,3]) -> [2, 3, 4] + * + * If `object` is supplied, it is the object of the call. + * + * The fusion rule: + * >> map('+1', map('*2', [1,2,3])) -> [3, 5, 7] + * >> map(compose('+1', '*2'), [1,2,3]) -> [3, 5, 7] + */ +Functional.map = function(fn, sequence, object) { + fn = Function.toFunction(fn); + var len = sequence.length, + result = new Array(len); + for (var i = 0; i < len; i++) + result[i] = fn.apply(object, [sequence[i], i]); + return result; +} + +/** + * Applies `fn` to `init` and the first element of `sequence`, + * and then to the result and the second element, and so on. + * == reduce(f, init, [x0, x1, x2]) == f(f(f(init, x0), x1), x2) + * :: (a b -> a) a [b] -> a + * >> reduce('x y -> 2*x+y', 0, [1,0,1,0]) -> 10 + */ +Functional.reduce = function(fn, init, sequence, object) { + fn = Function.toFunction(fn); + var len = sequence.length, + result = init; + for (var i = 0; i < len; i++) + result = fn.apply(object, [result, sequence[i]]); + return result; +} + +/** + * Returns a list of those elements $x$ of `sequence` such that + * $fn(x)$ returns true. + * :: (a -> boolean) [a] -> [a] + * >> select('%2', [1,2,3,4]) -> [1, 3] + */ +Functional.select = function(fn, sequence, object) { + fn = Function.toFunction(fn); + var len = sequence.length, + result = []; + for (var i = 0; i < len; i++) { + var x = sequence[i]; + fn.apply(object, [x, i]) && result.push(x); + } + return result; +} + +/// A synonym for `select`. +Functional.filter = Functional.select; + +/// A synonym for `reduce`. +Functional.foldl = Functional.reduce; + +/** + * Same as `foldl`, but applies the function from right to left. + * == foldr(f, init, [x0, x1, x2]) == fn(x0, f(x1, f(x2, init))) + * :: (a b -> b) b [a] -> b + * >> foldr('x y -> 2*x+y', 100, [1,0,1,0]) -> 104 + */ +Functional.foldr = function(fn, init, sequence, object) { + fn = Function.toFunction(fn); + var len = sequence.length, + result = init; + for (var i = len; --i >= 0; ) + result = fn.apply(object, [sequence[i], result]); + return result; +} + +/// ^^ Predicates + +/** + * Returns a function that returns `true` when all the arguments, applied + * to the returned function's arguments, returns true. + * == and(f1, f2...)(args...) == f1(args...) && f2(args...)... + * :: [a -> boolean] a -> a + * >> and('>1', '>2')(2) -> false + * >> and('>1', '>2')(3) -> true + * >> and('>1', 'error()')(1) -> false + */ +Functional.and = function(/*functions...*/) { + var args = Functional.map(Function.toFunction, arguments), + arglen = args.length; + return function() { + var value = true; + for (var i = 0; i < arglen; i++) + if (!(value = args[i].apply(this, arguments))) + break; + return value; + } +} + +/** + * Returns a function that returns `true` when any argument, applied + * to the returned function's arguments, returns true. + * == or(f1, f2...)(args...) == f1(args...) || f2(args...)... + * :: [a -> boolean] a -> a + * >> or('>1', '>2')(1) -> false + * >> or('>1', '>2')(2) -> true + * >> or('>1', 'error()')(2) -> true + */ +Functional.or = function(/*functions...*/) { + var args = Functional.map(Function.toFunction, arguments), + arglen = args.length; + return function() { + var value = false; + for (var i = 0; i < arglen; i++) + if ((value = args[i].apply(this, arguments))) + break; + return value; + } +} + +/** + * Returns true when $fn(x)$ returns true for some element $x$ of + * `sequence`. The returned function short-circuits. + * == some(f, [x1, x2, x3, ...]) == f(x1) || f(x2) || f(x3)... + * :: (a -> boolean) [a] -> boolean + * >> some('>2', [1,2,3]) -> true + * >> some('>10', [1,2,3]) -> false + */ +Functional.some = function(fn, sequence, object) { + fn = Function.toFunction(fn); + var len = sequence.length, + value = false; + for (var i = 0; i < len; i++) + if ((value = fn.call(object, sequence[i]))) + break; + return value; +} + +/** + * Returns true when $fn(x)$ returns true for every element $x$ of + * `sequence`. The returned function short-circuits. + * == every(f, [x1, x2, x3, ...]) == f(x1) && f(x2) && f(x3)... + * :: (a -> boolean) [a] -> boolean + * >> every('<2', [1,2,3]) -> false + * >> every('<10', [1,2,3]) -> true + */ +Functional.every = function(fn, sequence, object) { + fn = Function.toFunction(fn); + var len = sequence.length, + value = true; + for (var i = 0; i < len; i++) + if (!(value = fn.call(object, sequence[i]))) + break; + return value; +} + +/** + * Returns a function that returns `true` when $fn()$ returns false. + * == f.not()(args...) == !f(args...) + * :: (a -> boolean) -> (a -> boolean) + * >> not(Functional.K(true))() -> false + * >> not(Functional.K(false))() -> true + */ +Functional.not = function(fn) { + fn = Function.toFunction(fn); + return function() { + return !fn.apply(null, arguments); + } +} + +/** + * Returns a function that returns true when this function's arguments + * applied to that functions are always the same. The returned function + * short-circuits. + * == equal(f1, f2...)(args...) == f1(args...) == f2(args...)... + * :: [a... -> b] -> a... -> b + * >> equal()() -> true + * >> equal(K(1))() -> true + * >> equal(K(1), K(1))() -> true + * >> equal(K(1), K(2))() -> false + * >> equal(K(1), K(2), 'error()')() -> false + */ +Functional.equal = function(/*fn...*/) { + var arglen = arguments.length, + args = Functional.map(Function.toFunction, arguments); + if (!arglen) return Functional.K(true); + // if arglen == 1 it's also constant true, but + // call it for effect. + return function() { + var value = args[0].apply(this, arguments); + for (var i = 1; i < arglen; i++) + if (value != args[i].apply(this, args)) + return false; + return true; + } +} + + +/// ^^ Utilities + +/** + * Returns its argument coerced to a function. + * >> lambda('1+')(2) -> 3 + * >> lambda(function(n){return n+1})(2) -> 3 + */ +Functional.lambda = function(object) { + return object.toFunction(); +} + + +/** + * Returns a function that takes an object as an argument, and applies + * `object`'s `methodName` method to `arguments`. + * == invoke(name)(object, args...) == object[name](args...) + * :: name args... -> object args2... -> object[name](args... args2...) + * >> invoke('toString')(123) -> "123" + */ +Functional.invoke = function(methodName/*, arguments*/) { + var args = Array.slice(arguments, 1); + return function(object) { + return object[methodName].apply(object, Array.slice(arguments, 1).concat(args)); + } +} + +/** + * Returns a function that takes an object, and returns the value of its + * `name` property. `pluck(name)` is equivalent to `'_.name'.lambda()`. + * == pluck(name)(object) == object[name] + * :: name -> object -> object[name] + * >> pluck('length')("abc") -> 3 + */ +Functional.pluck = function(name) { + return function(object) { + return object[name]; + } +} + +/** + * Returns a function that, while $pred(value)$ is true, applies `fn` to + * $value$ to produce a new value, which is used as an input for the next round. + * The returned function returns the first $value$ for which $pred(value)$ + * is false. + * :: (a -> boolean) (a -> a) -> a + * >> until('>10', '2*')(1) -> 16 + */ +Functional.until = function(pred, fn) { + fn = Function.toFunction(fn); + pred = Function.toFunction(pred); + return function(value) { + while (!pred.call(null, value)) + value = fn.call(null, value); + return value; + } +} + +/** + * :: [a] [b]... -> [[a b]...] + * == zip(a, b...) == [[a0, b0], [a1, b1], ...] + * Did you know that `zip` can transpose a matrix? + * >> zip.apply(null, [[1,2],[3,4]]) -> [[1, 3], [2, 4]] + */ +Functional.zip = function(/*args...*/) { + var n = Math.min.apply(null, Functional.map('.length', arguments)); + var results = new Array(n); + for (var i = 0; i < n; i++) { + var key = String(i); + results[key] = Functional.map(pluck(key), arguments); + }; + return results; +} + +Functional._startRecordingMethodChanges = function(object) { + var initialMethods = {}; + for (var name in object) + initialMethods[name] = object[name]; + return {getChangedMethods: function() { + var changedMethods = {}; + for (var name in object) + if (object[name] != initialMethods[name]) + changedMethods[name] = object[name]; + return changedMethods; + }}; +} + +// For each method that this file defined on `Function.prototype`, +// define a function on `Functional` that delegates to it. +Functional._attachMethodDelegates = function(methods) { + for (var name in methods) + Functional[name] = Functional[name] || (function(name) { + var fn = methods[name]; + return function(object) { + return fn.apply(Function.toFunction(object), Array.slice(arguments, 1)); + } + })(name); +} + +// Record the current contents of `Function.prototype`, so that we +// can see what we've added later. +Functional.__initalFunctionState = Functional._startRecordingMethodChanges(Function.prototype); + +/// ^ Higher-order methods + +/// ^^ Partial function application + +/** + * Returns a bound method on `object`, optionally currying `args`. + * == f.bind(obj, args...)(args2...) == f.apply(obj, [args..., args2...]) + */ +Function.prototype.bind = function(object/*, args...*/) { + var fn = this; + var args = Array.slice(arguments, 1); + return function() { + return fn.apply(object, args.concat(Array.slice(arguments, 0))); + } +} + +/** + * Returns a function that applies the underlying function to `args`, and + * ignores its own arguments. + * :: (a... -> b) a... -> (... -> b) + * == f.saturate(args...)(args2...) == f(args...) + * >> Math.max.curry(1, 2)(3, 4) -> 4 + * >> Math.max.saturate(1, 2)(3, 4) -> 2 + * >> Math.max.curry(1, 2).saturate()(3, 4) -> 2 + */ +Function.prototype.saturate = function(/*args*/) { + var fn = this; + var args = Array.slice(arguments, 0); + return function() { + return fn.apply(this, args); + } +} + +/** + * Invoking the function returned by this function only passes `n` + * arguments to the underlying function. If the underlying function + * is not saturated, the result is a function that passes all its + * arguments to the underlying function. (That is, `aritize` only + * affects its immediate caller, and not subsequent calls.) + * >> '[a,b]'.lambda()(1,2) -> [1, 2] + * >> '[a,b]'.lambda().aritize(1)(1,2) -> [1, undefined] + * >> '+'.lambda()(1,2)(3) -> error + * >> '+'.lambda().ncurry(2).aritize(1)(1,2)(3) -> 4 + * + * `aritize` is useful to remove optional arguments from a function that + * is passed to a higher-order function that supplies *different* optional + * arguments. + * + * For example, many implementations of `map` and other collection + * functions, including those in this library, call the function argument + * with both the collection element + * and its position. This is convenient when expected, but can wreak + * havoc when the function argument is a curried function that expects + * a single argument from `map` and the remaining arguments from when + * the result of `map` is applied. + */ +Function.prototype.aritize = function(n) { + var fn = this; + return function() { + return fn.apply(this, Array.slice(arguments, 0, n)); + } +} + +/** + * Returns a function that, applied to an argument list $arg2$, + * applies the underlying function to $args ++ arg2$. + * :: (a... b... -> c) a... -> (b... -> c) + * == f.curry(args1...)(args2...) == f(args1..., args2...) + * + * Note that, unlike in languages with true partial application such as Haskell, + * `curry` and `uncurry` are not inverses. This is a repercussion of the + * fact that in JavaScript, unlike Haskell, a fully saturated function is + * not equivalent to the value that it returns. The definition of `curry` + * here matches semantics that most people have used when implementing curry + * for procedural languages. + * + * This implementation is adapted from + * [http://www.coryhudson.com/blog/2007/03/10/javascript-currying-redux/]. + */ +Function.prototype.curry = function(/*args...*/) { + var fn = this; + var args = Array.slice(arguments, 0); + return function() { + return fn.apply(this, args.concat(Array.slice(arguments, 0))); + }; +} + +/* + * Right curry. Returns a function that, applied to an argument list $args2$, + * applies the underlying function to $args2 + args$. + * == f.curry(args1...)(args2...) == f(args2..., args1...) + * :: (a... b... -> c) b... -> (a... -> c) + */ +Function.prototype.rcurry = function(/*args...*/) { + var fn = this; + var args = Array.slice(arguments, 0); + return function() { + return fn.apply(this, Array.slice(arguments, 0).concat(args)); + }; +} + +/** + * Same as `curry`, except only applies the function when all + * `n` arguments are saturated. + */ +Function.prototype.ncurry = function(n/*, args...*/) { + var fn = this; + var largs = Array.slice(arguments, 1); + return function() { + var args = largs.concat(Array.slice(arguments, 0)); + if (args.length < n) { + args.unshift(n); + return fn.ncurry.apply(fn, args); + } + return fn.apply(this, args); + }; +} + +/** + * Same as `rcurry`, except only applies the function when all + * `n` arguments are saturated. + */ +Function.prototype.rncurry = function(n/*, args...*/) { + var fn = this; + var rargs = Array.slice(arguments, 1); + return function() { + var args = Array.slice(arguments, 0).concat(rargs); + if (args.length < n) { + args.unshift(n); + return fn.rncurry.apply(fn, args); + } + return fn.apply(this, args); + }; +} + +/** + * `_` (underscore) is bound to a unique value for use in `partial`, below. + * This is a global variable, but it's also a property of `Function` in case + * you overwrite or bind over the global one. + */ +_ = Function._ = {}; + +/** + * Returns a function $f$ such that $f(args2)$ is equivalent to + * the underlying function applied to a combination of $args$ and $args2$. + * + * `args` is a partially-specified argument: it's a list with "holes", + * specified by the special value `_`. It is combined with $args2$ as + * follows: + * + * From left to right, each value in $args2$ fills in the leftmost + * remaining hole in `args`. Any remaining values + * in $args2$ are appended to the result of the filling-in process + * to produce the combined argument list. + * + * If the combined argument list contains any occurrences of `_`, the result + * of the application of $f$ is another partial function. Otherwise, the + * result is the same as the result of applying the underlying function to + * the combined argument list. + */ +Function.prototype.partial = function(/*args*/) { + var fn = this; + var _ = Function._; + var args = Array.slice(arguments, 0); + //substitution positions + var subpos = [], value; + for (var i = 0; i < arguments.length; i++) + arguments[i] == _ && subpos.push(i); + return function() { + var specialized = args.concat(Array.slice(arguments, subpos.length)); + for (var i = 0; i < Math.min(subpos.length, arguments.length); i++) + specialized[subpos[i]] = arguments[i]; + for (var i = 0; i < specialized.length; i++) + if (specialized[i] == _) + return fn.partial.apply(fn, specialized); + return fn.apply(this, specialized); + } +} + +/// ^^ Combinators + +/// ^^^ Combinator Functions + +/** + * The identity function: $x -> x$. + * == I(x) == x + * == I == 'x'.lambda() + * :: a -> a + * >> Functional.I(1) -> 1 + */ +Functional.I = function(x) {return x}; + +/** + * Returns a constant function that returns `x`. + * == K(x)(y) == x + * :: a -> b -> a + * >> Functional.K(1)(2) -> 1 + */ +Functional.K = function(x) {return function() {return x}}; + +/// A synonym for `Functional.I` +Functional.id = Functional.I; + +/// A synonym for `Functional.K` +Functional.constfn = Functional.K; + + +/** + * Returns a function that applies the first function to the + * result of the second, but passes all its arguments too. + * == S(f, g)(args...) == f(g(args...), args...) + * + * This is useful for composing functions when each needs access + * to the arguments to the composed function. For example, + * the following function multiples its last two arguments, + * and adds the first to that. + * >> Function.S('+', '_ a b -> a*b')(2,3,4) -> 14 + * + * Curry this to get a version that takes its arguments in + * separate calls: + * >> Function.S.curry('+')('_ a b -> a*b')(2,3,4) -> 14 + */ +Function.S = function(f, g) { + f = Function.toFunction(f); + g = Function.toFunction(g); + return function() { + return f.apply(this, [g.apply(this, arguments)].concat(Array.slice(arguments, 0))); + } +} + +/// ^^^ Combinator methods + +/** + * Returns a function that swaps its first two arguments before + * passing them to the underlying function. + * == f.flip()(a, b, c...) == f(b, a, c...) + * :: (a b c...) -> (b a c...) + * >> ('a/b'.lambda()).flip()(1,2) -> 2 + * + * For more general derangements, you can also use `prefilterSlice` + * with a string lambda: + * >> '100*a+10*b+c'.lambda().prefilterSlice('a b c -> [b, c, a]')(1,2,3) -> 231 + */ +Function.prototype.flip = function() { + var fn = this; + return function() { + var args = Array.slice(arguments, 0); + args = args.slice(1,2).concat(args.slice(0,1)).concat(args.slice(2)); + return fn.apply(this, args); + } +} + +/** + * Returns a function that applies the underlying function to its + * first argument, and the result of that application to the remaining + * arguments. + * == f.uncurry(a, b...) == f(a)(b...) + * :: (a -> b -> c) -> (a, b) -> c + * >> 'a -> b -> a/b'.lambda().uncurry()(1,2) -> 0.5 + * + * Note that `uncurry` is *not* the inverse of `curry`. + */ +Function.prototype.uncurry = function() { + var fn = this; + return function() { + var f1 = fn.apply(this, Array.slice(arguments, 0, 1)); + return f1.apply(this, Array.slice(arguments, 1)); + } +} + +/** + * ^^ Filtering + * + * Filters intercept a value before it is passed to a function, and apply the + * underlying function to the modified value. + */ + +/** + * `prefilterObject` returns a function that applies the underlying function + * to the same arguments, but to an object that is the result of appyling + * `filter` to the invocation object. + * == fn.prefilterObject(filter).apply(object, args...) == fn.apply(filter(object), args...) + * == fn.bind(object) == compose(fn.prefilterObject, Functional.K(object)) + * >> 'this'.lambda().prefilterObject('n+1').apply(1) -> 2 + */ +Function.prototype.prefilterObject = function(filter) { + filter = Function.toFunction(filter); + var fn = this; + return function() { + return fn.apply(filter(this), arguments); + } +} + +/** + * `prefilterAt` returns a function that applies the underlying function + * to a copy of the arguments, where the `index`th argument has been + * replaced by the value of `filter(argument[index])`. + * == fn.prefilterAt(i, filter)(a1, a2, ..., a_{n}) == fn(a1, a2, ..., filter(a_{i}), ..., a_{n}) + * >> '[a,b,c]'.lambda().prefilterAt(1, '2*')(2,3,4) -> [2, 6, 4] + */ +Function.prototype.prefilterAt = function(index, filter) { + filter = Function.toFunction(filter); + var fn = this; + return function() { + var args = Array.slice(arguments, 0); + args[index] = filter.call(this, args[index]); + return fn.apply(this, args); + } +} + +/** + * `prefilterSlice` returns a function that applies the underlying function + * to a copy of the arguments, where the arguments `start` through + * `end` have been replaced by the value of `filter(argument.slice(start,end))`, + * which must return a list. + * == fn.prefilterSlice(i0, i1, filter)(a1, a2, ..., a_{n}) == fn(a1, a2, ..., filter(args_{i0}, ..., args_{i1}), ..., a_{n}) + * >> '[a,b,c]'.lambda().prefilterSlice('[a+b]', 1, 3)(1,2,3,4) -> [1, 5, 4] + * >> '[a,b]'.lambda().prefilterSlice('[a+b]', 1)(1,2,3) -> [1, 5] + * >> '[a]'.lambda().prefilterSlice(compose('[_]', Math.max))(1,2,3) -> [3] + */ +Function.prototype.prefilterSlice = function(filter, start, end) { + filter = Function.toFunction(filter); + start = start || 0; + var fn = this; + return function() { + var args = Array.slice(arguments, 0); + var e = end < 0 ? args.length + end : end || args.length; + args.splice.apply(args, [start, (e||args.length)-start].concat(filter.apply(this, args.slice(start, e)))); + return fn.apply(this, args); + } +} + +/// ^^ Method Composition + +/** + * `compose` returns a function that applies the underlying function + * to the result of the application of `fn`. + * == f.compose(g)(args...) == f(g(args...)) + * >> '1+'.lambda().compose('2*')(3) -> 7 + * + * Note that, unlike `Functional.compose`, the `compose` method on + * function only takes a single argument. + * == Functional.compose(f, g) == f.compose(g) + * == Functional.compose(f, g, h) == f.compose(g).compose(h) + */ +Function.prototype.compose = function(fn) { + var self = this; + fn = Function.toFunction(fn); + return function() { + return self.apply(this, [fn.apply(this, arguments)]); + } +} + +/** + * `sequence` returns a function that applies the underlying function + * to the result of the application of `fn`. + * == f.sequence(g)(args...) == g(f(args...)) + * == f.sequence(g) == g.compose(f) + * >> '1+'.lambda().sequence('2*')(3) -> 8 + * + * Note that, unlike `Functional.compose`, the `sequence` method on + * function only takes a single argument. + * == Functional.sequence(f, g) == f.sequence(g) + * == Functional.sequence(f, g, h) == f.sequence(g).sequence(h) + */ +Function.prototype.sequence = function(fn) { + var self = this; + fn = Function.toFunction(fn); + return function() { + return fn.apply(this, [self.apply(this, arguments)]); + } +} + +/** + * Returns a function that is equivalent to the underlying function when + * `guard` returns true, and otherwise is equivalent to the application + * of `otherwise` to the same arguments. + * + * `guard` and `otherwise` default to `Functional.I`. `guard` with + * no arguments therefore returns a function that applies the + * underlying function to its value only if the value is true, + * and returns the value otherwise. + * == f.guard(g, h)(args...) == f(args...), when g(args...) is true + * == f.guard(g ,h)(args...) == h(args...), when g(args...) is false + * >> '[_]'.lambda().guard()(1) -> [1] + * >> '[_]'.lambda().guard()(null) -> null + * >> '[_]'.lambda().guard(null, Functional.K('n/a'))(null) -> "n/a" + * >> 'x+1'.lambda().guard('<10', Functional.K(null))(1) -> 2 + * >> 'x+1'.lambda().guard('<10', Functional.K(null))(10) -> null + * >> '/'.lambda().guard('p q -> q', Functional.K('n/a'))(1, 2) -> 0.5 + * >> '/'.lambda().guard('p q -> q', Functional.K('n/a'))(1, 0) -> "n/a" + * >> '/'.lambda().guard('p q -> q', '-> "n/a"')(1, 0) -> "n/a" + */ +Function.prototype.guard = function(guard, otherwise) { + var fn = this; + guard = Function.toFunction(guard || Functional.I); + otherwise = Function.toFunction(otherwise || Functional.I); + return function() { + return (guard.apply(this, arguments) ? fn : otherwise).apply(this, arguments); + } +} + +/// ^^ Utilities + +/** + * Returns a function identical to this function except that + * it prints its arguments on entry and its return value on exit. + * This is useful for debugging function-level programs. + */ +Function.prototype.traced = function(name) { + var self = this, + global = (function() { return this; })(), + log = function() {}; + + if (typeof console != 'undefined' && typeof console.info == 'function') { + log = console.info; + } else if (typeof print == 'function') { + log = print; + } + + name = name || self; + return function() { + log('[', name, 'apply(', this!=global && this, ',', arguments, ')'); + var result = self.apply(this, arguments); + log(']', name, ' -> ', result); + return result; + } +} + + +/** + * ^^ Function methods as functions + * + * In addition to the functions defined above, every method defined + * on `Function` is also available as a function in `Functional`, that + * coerces its first argument to a `Function` and applies + * the remaining arguments to this. + * + * A few examples make this clearer: + * == curry(fn, args...) == fn.curry(args...) + * >> Functional.flip('a/b')(1, 2) -> 2 + * >> Functional.curry('a/b', 1)(2) -> 0.5 + + * For each method that this file defined on Function.prototype, + * define a function on Functional that delegates to it. + */ +Functional._attachMethodDelegates(Functional.__initalFunctionState.getChangedMethods()); +delete Functional.__initalFunctionState; + + +// In case to-function.js isn't loaded. +Function.toFunction = Function.toFunction || Functional.K; + +if (!Array.slice) { // mozilla already supports this + Array.slice = (function(slice) { + return function(object) { + return slice.apply(object, slice.call(arguments, 1)); + }; + })(Array.prototype.slice); +} +/* + * Author: Oliver Steele + * Copyright: Copyright 2007 by Oliver Steele. All rights reserved. + * License: MIT License + * Homepage: http://osteele.com/javascripts/functional + * Created: 2007-07-11 + * Version: 1.0.2 + * + * + * This defines "string lambdas", that allow strings such as `x+1` and + * `x -> x+1` to be used in some contexts as functions. + */ + + +/// ^ String lambdas + +/** + * Turns a string that contains a JavaScript expression into a + * `Function` that returns the value of that expression. + * + * If the string contains a `->`, this separates the parameters from the body: + * >> 'x -> x + 1'.lambda()(1) -> 2 + * >> 'x y -> x + 2*y'.lambda()(1, 2) -> 5 + * >> 'x, y -> x + 2*y'.lambda()(1, 2) -> 5 + * + * Otherwise, if the string contains a `_`, this is the parameter: + * >> '_ + 1'.lambda()(1) -> 2 + * + * Otherwise if the string begins or ends with an operator or relation, + * prepend or append a parameter. (The documentation refers to this type + * of string as a "section".) + * >> '/2'.lambda()(4) -> 2 + * >> '2/'.lambda()(4) -> 0.5 + * >> '/'.lambda()(2,4) -> 0.5 + * Sections can end, but not begin with, `-`. (This is to avoid interpreting + * e.g. `-2*x` as a section). On the other hand, a string that either begins + * or ends with `/` is a section, so an expression that begins or ends with a + * regular expression literal needs an explicit parameter. + * + * Otherwise, each variable name is an implicit parameter: + * >> 'x + 1'.lambda()(1) -> 2 + * >> 'x + 2*y'.lambda()(1, 2) -> 5 + * >> 'y + 2*x'.lambda()(1, 2) -> 5 + * + * Implicit parameter detection ignores strings literals, variable names that + * start with capitals, and identifiers that precede `:` or follow `.`: + * >> map('"im"+root', ["probable", "possible"]) -> ["improbable", "impossible"] + * >> 'Math.cos(angle)'.lambda()(Math.PI) -> -1 + * >> 'point.x'.lambda()({x:1, y:2}) -> 1 + * >> '({x:1, y:2})[key]'.lambda()('x') -> 1 + * + * Implicit parameter detection mistakenly looks inside regular expression + * literals for variable names. It also doesn't know to ignore JavaScript + * keywords and bound variables. (The only way you can get these last two is + * with a function literal inside the string. This is outside the intended use + * case for string lambdas.) + * + * Use `_` (to define a unary function) or `->`, if the string contains anything + * that looks like a free variable but shouldn't be used as a parameter, or + * to specify parameters that are ordered differently from their first + * occurrence in the string. + * + * Chain `->`s to create a function in uncurried form: + * >> 'x -> y -> x + 2*y'.lambda()(1)(2) -> 5 + * >> 'x -> y -> z -> x + 2*y+3*z'.lambda()(1)(2)(3) -> 14 + * + * `this` and `arguments` are special: + * >> 'this'.call(1) -> 1 + * >> '[].slice.call(arguments, 0)'.call(null,1,2) -> [1, 2] + */ +String.prototype.lambda = function() { + var params = [], + expr = this, + sections = expr.ECMAsplit(/\s*->\s*/m); + if (sections.length > 1) { + while (sections.length) { + expr = sections.pop(); + params = sections.pop().split(/\s*,\s*|\s+/m); + sections.length && sections.push('(function('+params+'){return ('+expr+')})'); + } + } else if (expr.match(/\b_\b/)) { + params = '_'; + } else { + // test whether an operator appears on the left (or right), respectively + var leftSection = expr.match(/^\s*(?:[+*\/%&|\^\.=<>]|!=)/m), + rightSection = expr.match(/[+\-*\/%&|\^\.=<>!]\s*$/m); + if (leftSection || rightSection) { + if (leftSection) { + params.push('$1'); + expr = '$1' + expr; + } + if (rightSection) { + params.push('$2'); + expr = expr + '$2'; + } + } else { + // `replace` removes symbols that are capitalized, follow '.', + // precede ':', are 'this' or 'arguments'; and also the insides of + // strings (by a crude test). `match` extracts the remaining + // symbols. + var vars = this.replace(/(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*|[a-zA-Z_$][a-zA-Z_$\d]*\s*:|this|arguments|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, '').match(/([a-z_$][a-z_$\d]*)/gi) || []; // ' + for (var i = 0, v; v = vars[i++]; ) + params.indexOf(v) >= 0 || params.push(v); + } + } + return new Function(params, 'return (' + expr + ')'); +} + +/// Turn on caching for `string` -> `Function` conversion. +String.prototype.lambda.cache = function() { + var proto = String.prototype, + cache = {}, + uncached = proto.lambda, + cached = function() { + var key = '#' + this; // avoid hidden properties on Object.prototype + return cache[key] || (cache[key] = uncached.call(this)); + }; + cached.cached = function(){}; + cached.uncache = function(){proto.lambda = uncached}; + proto.lambda = cached; +} + +/** + * ^^ Duck-Typing + * + * Strings support `call` and `apply`. This duck-types them as + * functions, to some callers. + */ + +/** + * Coerce the string to a function and then apply it. + * >> 'x+1'.apply(null, [2]) -> 3 + * >> '/'.apply(null, [2, 4]) -> 0.5 + */ +String.prototype.apply = function(thisArg, args) { + return this.toFunction().apply(thisArg, args); +} + +/** + * Coerce the string to a function and then call it. + * >> 'x+1'.call(null, 2) -> 3 + * >> '/'.call(null, 2, 4) -> 0.5 + */ +String.prototype.call = function() { + return this.toFunction().apply(arguments[0], + Array.prototype.slice.call(arguments, 1)); +} + +/// ^^ Coercion + +/** + * Returns a `Function` that perfoms the action described by this + * string. If the string contains a `return`, applies + * `new Function` to it. Otherwise, this function returns + * the result of `this.lambda()`. + * >> '+1'.toFunction()(2) -> 3 + * >> 'return 1'.toFunction()(1) -> 1 + */ +String.prototype.toFunction = function() { + var body = this; + if (body.match(/\breturn\b/)) + return new Function(this); + return this.lambda(); +} + +/** + * Returns this function. `Function.toFunction` calls this. + * >> '+1'.lambda().toFunction()(2) -> 3 + */ +Function.prototype.toFunction = function() { + return this; +} + +/** + * Coerces `fn` into a function if it is not already one, + * by calling its `toFunction` method. + * >> Function.toFunction(function() {return 1})() -> 1 + * >> Function.toFunction('+1')(2) -> 3 + * + * `Function.toFunction` requires an argument that can be + * coerced to a function. A nullary version can be + * constructed via `guard`: + * >> Function.toFunction.guard()('1+') -> function() + * >> Function.toFunction.guard()(null) -> null + * + * `Function.toFunction` doesn't coerce arbitrary values to functions. + * It might seem convenient to treat + * `Function.toFunction(value)` as though it were the + * constant function that returned `value`, but it's rarely + * useful and it hides errors. Use `Functional.K(value)` instead, + * or a lambda string when the value is a compile-time literal: + * >> Functional.K('a string')() -> "a string" + * >> Function.toFunction('"a string"')() -> "a string" + */ +Function.toFunction = function(value) { + return value.toFunction(); +} + +// Utilities + +// IE6 split is not ECMAScript-compliant. This breaks '->1'.lambda(). +// ECMAsplit is an ECMAScript-compliant `split`, although only for +// one argument. +String.prototype.ECMAsplit = + // The test is from the ECMAScript reference. + ('ab'.split(/a*/).length > 1 + ? String.prototype.split + : function(separator, limit) { + if (typeof limit != 'undefined') + throw "ECMAsplit: limit is unimplemented"; + var result = this.split.apply(this, arguments), + re = RegExp(separator), + savedIndex = re.lastIndex, + match = re.exec(this); + if (match && match.index == 0) + result.unshift(''); + // in case `separator` was already a RegExp: + re.lastIndex = savedIndex; + return result; + }); diff --git a/lib/functional/to-function.js b/lib/functional/to-function.js new file mode 100644 index 0000000..936376b --- /dev/null +++ b/lib/functional/to-function.js @@ -0,0 +1,221 @@ +/* + * Author: Oliver Steele + * Copyright: Copyright 2007 by Oliver Steele. All rights reserved. + * License: MIT License + * Homepage: http://osteele.com/javascripts/functional + * Created: 2007-07-11 + * Version: 1.0.2 + * + * + * This defines "string lambdas", that allow strings such as `x+1` and + * `x -> x+1` to be used in some contexts as functions. + */ + + +/// ^ String lambdas + +/** + * Turns a string that contains a JavaScript expression into a + * `Function` that returns the value of that expression. + * + * If the string contains a `->`, this separates the parameters from the body: + * >> 'x -> x + 1'.lambda()(1) -> 2 + * >> 'x y -> x + 2*y'.lambda()(1, 2) -> 5 + * >> 'x, y -> x + 2*y'.lambda()(1, 2) -> 5 + * + * Otherwise, if the string contains a `_`, this is the parameter: + * >> '_ + 1'.lambda()(1) -> 2 + * + * Otherwise if the string begins or ends with an operator or relation, + * prepend or append a parameter. (The documentation refers to this type + * of string as a "section".) + * >> '/2'.lambda()(4) -> 2 + * >> '2/'.lambda()(4) -> 0.5 + * >> '/'.lambda()(2,4) -> 0.5 + * Sections can end, but not begin with, `-`. (This is to avoid interpreting + * e.g. `-2*x` as a section). On the other hand, a string that either begins + * or ends with `/` is a section, so an expression that begins or ends with a + * regular expression literal needs an explicit parameter. + * + * Otherwise, each variable name is an implicit parameter: + * >> 'x + 1'.lambda()(1) -> 2 + * >> 'x + 2*y'.lambda()(1, 2) -> 5 + * >> 'y + 2*x'.lambda()(1, 2) -> 5 + * + * Implicit parameter detection ignores strings literals, variable names that + * start with capitals, and identifiers that precede `:` or follow `.`: + * >> map('"im"+root', ["probable", "possible"]) -> ["improbable", "impossible"] + * >> 'Math.cos(angle)'.lambda()(Math.PI) -> -1 + * >> 'point.x'.lambda()({x:1, y:2}) -> 1 + * >> '({x:1, y:2})[key]'.lambda()('x') -> 1 + * + * Implicit parameter detection mistakenly looks inside regular expression + * literals for variable names. It also doesn't know to ignore JavaScript + * keywords and bound variables. (The only way you can get these last two is + * with a function literal inside the string. This is outside the intended use + * case for string lambdas.) + * + * Use `_` (to define a unary function) or `->`, if the string contains anything + * that looks like a free variable but shouldn't be used as a parameter, or + * to specify parameters that are ordered differently from their first + * occurrence in the string. + * + * Chain `->`s to create a function in uncurried form: + * >> 'x -> y -> x + 2*y'.lambda()(1)(2) -> 5 + * >> 'x -> y -> z -> x + 2*y+3*z'.lambda()(1)(2)(3) -> 14 + * + * `this` and `arguments` are special: + * >> 'this'.call(1) -> 1 + * >> '[].slice.call(arguments, 0)'.call(null,1,2) -> [1, 2] + */ +String.prototype.lambda = function() { + var params = [], + expr = this, + sections = expr.ECMAsplit(/\s*->\s*/m); + if (sections.length > 1) { + while (sections.length) { + expr = sections.pop(); + params = sections.pop().split(/\s*,\s*|\s+/m); + sections.length && sections.push('(function('+params+'){return ('+expr+')})'); + } + } else if (expr.match(/\b_\b/)) { + params = '_'; + } else { + // test whether an operator appears on the left (or right), respectively + var leftSection = expr.match(/^\s*(?:[+*\/%&|\^\.=<>]|!=)/m), + rightSection = expr.match(/[+\-*\/%&|\^\.=<>!]\s*$/m); + if (leftSection || rightSection) { + if (leftSection) { + params.push('$1'); + expr = '$1' + expr; + } + if (rightSection) { + params.push('$2'); + expr = expr + '$2'; + } + } else { + // `replace` removes symbols that are capitalized, follow '.', + // precede ':', are 'this' or 'arguments'; and also the insides of + // strings (by a crude test). `match` extracts the remaining + // symbols. + var vars = this.replace(/(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*|[a-zA-Z_$][a-zA-Z_$\d]*\s*:|this|arguments|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, '').match(/([a-z_$][a-z_$\d]*)/gi) || []; // ' + for (var i = 0, v; v = vars[i++]; ) + params.indexOf(v) >= 0 || params.push(v); + } + } + return new Function(params, 'return (' + expr + ')'); + // return eval('(function('+params+'){return ('+expr+'); })'); +} + +/// Turn on caching for `string` -> `Function` conversion. +String.prototype.lambda.cache = function() { + var proto = String.prototype, + cache = {}, + uncached = proto.lambda, + cached = function() { + var key = '#' + this; // avoid hidden properties on Object.prototype + return cache[key] || (cache[key] = uncached.call(this)); + }; + cached.cached = function(){}; + cached.uncache = function(){proto.lambda = uncached}; + proto.lambda = cached; +} + +/** + * ^^ Duck-Typing + * + * Strings support `call` and `apply`. This duck-types them as + * functions, to some callers. + */ + +/** + * Coerce the string to a function and then apply it. + * >> 'x+1'.apply(null, [2]) -> 3 + * >> '/'.apply(null, [2, 4]) -> 0.5 + */ +String.prototype.apply = function(thisArg, args) { + return this.toFunction().apply(thisArg, args); +} + +/** + * Coerce the string to a function and then call it. + * >> 'x+1'.call(null, 2) -> 3 + * >> '/'.call(null, 2, 4) -> 0.5 + */ +String.prototype.call = function() { + return this.toFunction().apply(arguments[0], + Array.prototype.slice.call(arguments, 1)); +} + +/// ^^ Coercion + +/** + * Returns a `Function` that perfoms the action described by this + * string. If the string contains a `return`, applies + * `new Function` to it. Otherwise, this function returns + * the result of `this.lambda()`. + * >> '+1'.toFunction()(2) -> 3 + * >> 'return 1'.toFunction()(1) -> 1 + */ +String.prototype.toFunction = function() { + var body = this; + if (body.match(/\breturn\b/)) + return new Function(this); + return this.lambda(); +} + +/** + * Returns this function. `Function.toFunction` calls this. + * >> '+1'.lambda().toFunction()(2) -> 3 + */ +Function.prototype.toFunction = function() { + return this; +} + +/** + * Coerces `fn` into a function if it is not already one, + * by calling its `toFunction` method. + * >> Function.toFunction(function() {return 1})() -> 1 + * >> Function.toFunction('+1')(2) -> 3 + * + * `Function.toFunction` requires an argument that can be + * coerced to a function. A nullary version can be + * constructed via `guard`: + * >> Function.toFunction.guard()('1+') -> function() + * >> Function.toFunction.guard()(null) -> null + * + * `Function.toFunction` doesn't coerce arbitrary values to functions. + * It might seem convenient to treat + * `Function.toFunction(value)` as though it were the + * constant function that returned `value`, but it's rarely + * useful and it hides errors. Use `Functional.K(value)` instead, + * or a lambda string when the value is a compile-time literal: + * >> Functional.K('a string')() -> "a string" + * >> Function.toFunction('"a string"')() -> "a string" + */ +Function.toFunction = function(value) { + return value.toFunction(); +} + +// Utilities + +// IE6 split is not ECMAScript-compliant. This breaks '->1'.lambda(). +// ECMAsplit is an ECMAScript-compliant `split`, although only for +// one argument. +String.prototype.ECMAsplit = + // The test is from the ECMAScript reference. + ('ab'.split(/a*/).length > 1 + ? String.prototype.split + : function(separator, limit) { + if (typeof limit != 'undefined') + throw "ECMAsplit: limit is unimplemented"; + var result = this.split.apply(this, arguments), + re = RegExp(separator), + savedIndex = re.lastIndex, + match = re.exec(this); + if (match && match.index == 0) + result.unshift(''); + // in case `separator` was already a RegExp: + re.lastIndex = savedIndex; + return result; + }); diff --git a/src/Y/index.cjs b/src/Y/index.cjs index 4b3e16b..bc8097e 100644 --- a/src/Y/index.cjs +++ b/src/Y/index.cjs @@ -1,7 +1,7 @@ /// Import our external deps first /// -require('lessly/future'); -require('functional/to-function'); +// require('lessly/future'); +// require('functional/to-function'); /// Set up core and utilities /// var core = require('Y/core') diff --git a/src/Y/tofunction.cjs b/src/Y/tofunction.cjs deleted file mode 100644 index 936376b..0000000 --- a/src/Y/tofunction.cjs +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Author: Oliver Steele - * Copyright: Copyright 2007 by Oliver Steele. All rights reserved. - * License: MIT License - * Homepage: http://osteele.com/javascripts/functional - * Created: 2007-07-11 - * Version: 1.0.2 - * - * - * This defines "string lambdas", that allow strings such as `x+1` and - * `x -> x+1` to be used in some contexts as functions. - */ - - -/// ^ String lambdas - -/** - * Turns a string that contains a JavaScript expression into a - * `Function` that returns the value of that expression. - * - * If the string contains a `->`, this separates the parameters from the body: - * >> 'x -> x + 1'.lambda()(1) -> 2 - * >> 'x y -> x + 2*y'.lambda()(1, 2) -> 5 - * >> 'x, y -> x + 2*y'.lambda()(1, 2) -> 5 - * - * Otherwise, if the string contains a `_`, this is the parameter: - * >> '_ + 1'.lambda()(1) -> 2 - * - * Otherwise if the string begins or ends with an operator or relation, - * prepend or append a parameter. (The documentation refers to this type - * of string as a "section".) - * >> '/2'.lambda()(4) -> 2 - * >> '2/'.lambda()(4) -> 0.5 - * >> '/'.lambda()(2,4) -> 0.5 - * Sections can end, but not begin with, `-`. (This is to avoid interpreting - * e.g. `-2*x` as a section). On the other hand, a string that either begins - * or ends with `/` is a section, so an expression that begins or ends with a - * regular expression literal needs an explicit parameter. - * - * Otherwise, each variable name is an implicit parameter: - * >> 'x + 1'.lambda()(1) -> 2 - * >> 'x + 2*y'.lambda()(1, 2) -> 5 - * >> 'y + 2*x'.lambda()(1, 2) -> 5 - * - * Implicit parameter detection ignores strings literals, variable names that - * start with capitals, and identifiers that precede `:` or follow `.`: - * >> map('"im"+root', ["probable", "possible"]) -> ["improbable", "impossible"] - * >> 'Math.cos(angle)'.lambda()(Math.PI) -> -1 - * >> 'point.x'.lambda()({x:1, y:2}) -> 1 - * >> '({x:1, y:2})[key]'.lambda()('x') -> 1 - * - * Implicit parameter detection mistakenly looks inside regular expression - * literals for variable names. It also doesn't know to ignore JavaScript - * keywords and bound variables. (The only way you can get these last two is - * with a function literal inside the string. This is outside the intended use - * case for string lambdas.) - * - * Use `_` (to define a unary function) or `->`, if the string contains anything - * that looks like a free variable but shouldn't be used as a parameter, or - * to specify parameters that are ordered differently from their first - * occurrence in the string. - * - * Chain `->`s to create a function in uncurried form: - * >> 'x -> y -> x + 2*y'.lambda()(1)(2) -> 5 - * >> 'x -> y -> z -> x + 2*y+3*z'.lambda()(1)(2)(3) -> 14 - * - * `this` and `arguments` are special: - * >> 'this'.call(1) -> 1 - * >> '[].slice.call(arguments, 0)'.call(null,1,2) -> [1, 2] - */ -String.prototype.lambda = function() { - var params = [], - expr = this, - sections = expr.ECMAsplit(/\s*->\s*/m); - if (sections.length > 1) { - while (sections.length) { - expr = sections.pop(); - params = sections.pop().split(/\s*,\s*|\s+/m); - sections.length && sections.push('(function('+params+'){return ('+expr+')})'); - } - } else if (expr.match(/\b_\b/)) { - params = '_'; - } else { - // test whether an operator appears on the left (or right), respectively - var leftSection = expr.match(/^\s*(?:[+*\/%&|\^\.=<>]|!=)/m), - rightSection = expr.match(/[+\-*\/%&|\^\.=<>!]\s*$/m); - if (leftSection || rightSection) { - if (leftSection) { - params.push('$1'); - expr = '$1' + expr; - } - if (rightSection) { - params.push('$2'); - expr = expr + '$2'; - } - } else { - // `replace` removes symbols that are capitalized, follow '.', - // precede ':', are 'this' or 'arguments'; and also the insides of - // strings (by a crude test). `match` extracts the remaining - // symbols. - var vars = this.replace(/(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*|[a-zA-Z_$][a-zA-Z_$\d]*\s*:|this|arguments|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, '').match(/([a-z_$][a-z_$\d]*)/gi) || []; // ' - for (var i = 0, v; v = vars[i++]; ) - params.indexOf(v) >= 0 || params.push(v); - } - } - return new Function(params, 'return (' + expr + ')'); - // return eval('(function('+params+'){return ('+expr+'); })'); -} - -/// Turn on caching for `string` -> `Function` conversion. -String.prototype.lambda.cache = function() { - var proto = String.prototype, - cache = {}, - uncached = proto.lambda, - cached = function() { - var key = '#' + this; // avoid hidden properties on Object.prototype - return cache[key] || (cache[key] = uncached.call(this)); - }; - cached.cached = function(){}; - cached.uncache = function(){proto.lambda = uncached}; - proto.lambda = cached; -} - -/** - * ^^ Duck-Typing - * - * Strings support `call` and `apply`. This duck-types them as - * functions, to some callers. - */ - -/** - * Coerce the string to a function and then apply it. - * >> 'x+1'.apply(null, [2]) -> 3 - * >> '/'.apply(null, [2, 4]) -> 0.5 - */ -String.prototype.apply = function(thisArg, args) { - return this.toFunction().apply(thisArg, args); -} - -/** - * Coerce the string to a function and then call it. - * >> 'x+1'.call(null, 2) -> 3 - * >> '/'.call(null, 2, 4) -> 0.5 - */ -String.prototype.call = function() { - return this.toFunction().apply(arguments[0], - Array.prototype.slice.call(arguments, 1)); -} - -/// ^^ Coercion - -/** - * Returns a `Function` that perfoms the action described by this - * string. If the string contains a `return`, applies - * `new Function` to it. Otherwise, this function returns - * the result of `this.lambda()`. - * >> '+1'.toFunction()(2) -> 3 - * >> 'return 1'.toFunction()(1) -> 1 - */ -String.prototype.toFunction = function() { - var body = this; - if (body.match(/\breturn\b/)) - return new Function(this); - return this.lambda(); -} - -/** - * Returns this function. `Function.toFunction` calls this. - * >> '+1'.lambda().toFunction()(2) -> 3 - */ -Function.prototype.toFunction = function() { - return this; -} - -/** - * Coerces `fn` into a function if it is not already one, - * by calling its `toFunction` method. - * >> Function.toFunction(function() {return 1})() -> 1 - * >> Function.toFunction('+1')(2) -> 3 - * - * `Function.toFunction` requires an argument that can be - * coerced to a function. A nullary version can be - * constructed via `guard`: - * >> Function.toFunction.guard()('1+') -> function() - * >> Function.toFunction.guard()(null) -> null - * - * `Function.toFunction` doesn't coerce arbitrary values to functions. - * It might seem convenient to treat - * `Function.toFunction(value)` as though it were the - * constant function that returned `value`, but it's rarely - * useful and it hides errors. Use `Functional.K(value)` instead, - * or a lambda string when the value is a compile-time literal: - * >> Functional.K('a string')() -> "a string" - * >> Function.toFunction('"a string"')() -> "a string" - */ -Function.toFunction = function(value) { - return value.toFunction(); -} - -// Utilities - -// IE6 split is not ECMAScript-compliant. This breaks '->1'.lambda(). -// ECMAsplit is an ECMAScript-compliant `split`, although only for -// one argument. -String.prototype.ECMAsplit = - // The test is from the ECMAScript reference. - ('ab'.split(/a*/).length > 1 - ? String.prototype.split - : function(separator, limit) { - if (typeof limit != 'undefined') - throw "ECMAsplit: limit is unimplemented"; - var result = this.split.apply(this, arguments), - re = RegExp(separator), - savedIndex = re.lastIndex, - match = re.exec(this); - if (match && match.index == 0) - result.unshift(''); - // in case `separator` was already a RegExp: - re.lastIndex = savedIndex; - return result; - }); -- 1.7.0.4