Repository: raganwald/allong.es Branch: master Commit: 92b19a9993af Files: 37 Total size: 183.9 KB Directory structure: gitextract_ii3k_5xa/ ├── .gitignore ├── Gruntfile.coffee ├── README.md ├── _site/ │ ├── Gruntfile.coffee │ ├── README.md │ ├── functions.txt │ ├── lib/ │ │ └── allong.es.js │ ├── package.json │ ├── release_notes.md │ └── spec/ │ ├── apply.spec.coffee │ ├── arity.spec.coffee │ ├── basic.spec.coffee │ ├── callbacks.spec.coffee │ ├── core.spec.coffee │ ├── decorating-classes.spec.coffee │ ├── folding.spec.coffee │ ├── iterators.spec.coffee │ ├── promises.spec.coffee │ └── trampoline.spec.coffee ├── hello/ │ └── world.txt ├── lib/ │ └── allong.es.js ├── package.json └── spec/ ├── andand-oror.spec.coffee ├── apply.spec.coffee ├── arity.spec.coffee ├── core.spec.coffee ├── decorating-classes.spec.coffee ├── folding.spec.coffee ├── iterators.spec.coffee ├── map-arguments-with.spec.coffee ├── sequence-objects.spec.coffee ├── sequence.advanced.spec.coffee ├── sequence.callback.spec.coffee ├── sequence.decorataors.spec.coffee ├── sequence.then.spec.coffee ├── sequence.transformers.spec.coffee └── trampoline.spec.coffee ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store ================================================ FILE: Gruntfile.coffee ================================================ module.exports = (grunt) -> grunt.initConfig pkg: grunt.file.readJSON('package.json'), uglify: options: banner: '/*! http://github.com/raganwald/<%= pkg.name %> v<%= pkg.version %> <%= grunt.template.today("yyyy-mm-dd") %> (c) 2012-2013 Reg Braithwaite MIT Licensed */\n' build: src: 'lib/<%= pkg.name %>.js', dest: 'lib/<%= pkg.name %>.min.js' grunt.loadNpmTasks('grunt-contrib-uglify') grunt.registerTask('default', ['uglify']) ================================================ FILE: README.md ================================================ # `allong.es` The `allong.es` library is a collection of functions designed to facilitate writing JavaScript and/or CoffeeScript with functions as first-class values. The emphasis in `allong.es` is on composing and decomposing functions using combinators and decorators. `allong.es` is designed to complement libraries like [Underscore](http://underscorejs.org), not compete with them. ## Currying and Partial Application At the heart of `allong.es` are the functions that curry and partially apply other functions. The two most important to understand are `call` and `apply`. They work very much like the `.call` and `.apply` methods that every JavaScript function implements: ```javascript function greet (how, whom) { return '' + how + ', ' + whom + '!'; }; call(greet, 'Hello', 'Tom') //=> 'Hello, Tom!' apply(greet, ['Hello', 'Tom']) //=> 'Hello, Tom!' ``` Their "special sauce" is that they automatically *curry* the supplied function, so if you provide fewer or no arguments, you get back a partially applied or curried function: ```javascript call(greet)('Hello')('Tom') //=> 'Hello, Tom!' call(greet, 'Hello')('Tom') //=> 'Hello, Tom!' apply(greet, [])('Hello')('Tom') //=> 'Hello, Tom!' apply(greet, ['Hello'])('Tom') //=> 'Hello, Tom!' ``` ### immediate application If you don't want the currying/partial application behaviour, there is an immediate application version named (appropriately), `callNow` (and also another named `applyNow`, not shown): ```javascript callNow(greet, 'Hello', 'Tom') //=> 'Hello, Tom!' callNow(greet, 'Hello') //=> 'Hello, undefined!' ``` ### variations on the order of applying the arguments `callRight` applies any arguments supplied to the right. If you supply all the arguments, it's the same as `call`, but if you supply fewer arguments, you get a right partial application: ```javascript callRight(greet, 'Hello', 'Tom') //=> 'Hello, Tom!' callRight(greet, 'Hello')('Tom') //=> 'Tom, Hello!' ``` `callFlipped` applies the arguments backwards, even when curried: ```javascript callFlipped(greet, 'Hello', 'Tom') //=> 'Tom, Hello!' callFlipped(greet, 'Hello')('Tom') //=> 'Tom, Hello!' callFlipped(greet)('Hello')('Tom') //=> 'Tom, Hello!' ``` ### more partial application `callLeft` is actually synonymous with `call`: It applies arguments given to the left. We've seen `callRight` above. Both are *variadic*: You can supply as many arguments as you want. `callFirst` and `callLast` are just like `callLeft` and `callRight`, but they are *binary* functions: They accept a function and exactly one argument. This is sometimes useful when combining functions together. `callFirst` and `callLast` both have "flipped and curried" versions (`callFirstWith` and `callLastWith`). `callLastWith` is especially useful for working with functions written in "collection - operation" style. Here we take advantage of the fact that they are "automatically curried" to implement the popular `pluck` function. ### currying `allong.es` does support the `curry` function, it is implemented as the unary form of `call`: ```javascript var curry = unary(call); ``` ### with `splat` was present in earlier versions of `allong.es` but has been deprecated as being too cryptic. Instead, there is a general naming convention that works as follows. Many binary functions such as `map` and `filter` are historically written to take a noun or collection as the first argument and a verb as the second. However, reversing and currying these functions is super-useful as it makes composeable functions out of them. That's why `callFlipped` is so important. But to save you the trouble of writing `callFlipped map` everywhere, many such functions in `allong.es` have a clipped version pre-defined and named with the suffix `With`: ``` map(list, function) <=> mapWith(function, list) filter(list, function) <=> filterWith(function, list) get(object, propertyName) <=> getWith(propertyName, object) pluck(list, propertyName) <=> pluckWith(propertyName, list) ``` So you "map" a list, but "mapWith" a function. And of course, they are all curried. For example: ``` map(list)(function) <=> mapWith(function)(list) deepMap(list)(function) <=> deepMapWith(function)(list) filter(list)(function) <=> filterWith(function)(list) get(object)(propertyName) <=> getWith(propertyName)(object) pluck(list)(propertyName) <=> pluckWith(propertyName)(list) ``` Thus if you have a collection such as: ```javascript var users = [ { name: 'Huey' }, { name: 'Dewey' }, { name: 'Louie' } ] ``` You can get the names with either: ```javascript pluck(users, 'name') //=> ['Huey', 'Dewey', 'Louie'] ``` Or: ```javascript pluckWith('name', users) //=> ['Huey', 'Dewey', 'Louie'] ``` The latter is interesting because `pluck` and `pluckWith` are both automatically curried (like almost everything that isn't named "now"). Thus, we could also write: ```javascript var namesOf = pluckWith('name'); // ... namesOf(users) //=> ['Huey', 'Dewey', 'Louie'] ``` ## Arity Function Decorators ### variadic Makes a function into a variadic (accepts any number of arguments). The last named parameter will be given an array of arguments. ```javascript var variadic = require('allong.es').allong.es.variadic; var fn = variadic(function (a) { return a }) fn() //=> [] fn(1, 2, 3) //=> [1,2,3] fn = variadic(function (a,b) { return { a: a, b: b } }) fn() //=> { a: undefined, b: [] } fn(1) //=> { a: 1, b: [] } fn(1,2,3) //=> { a: 1, b: [2, 3] } ``` ### variadic, part ii When given just the function, `variadic` returns a function with an arity of zero. This is consistent with JavaScript programming practice. There are times when you wish to report an arity, meaning that you want the returned function to have its `length` getibute set. You do this by prefacing the function argument with a length: ```javascript fn = variadic(function (a,b) { return { a: a, b: b } }); fn.length //=> 0 fn2 = variadic(1, function (a,b) { return { a: a, b: b } }); fn2.length //=> 1 ``` ### unary, binary, and ternary Sometimes, you have a function that takes multiple arguments, but you only want it to accept one, or two, or maybe three arguments and ignore the rest. For example, `parseInt` takes a radix as an optional second parameter. And that is havoc if you try to use it with `Array.map`: ```javascript ['1', '2', '3', '4', '5'].map(parseInt) //=> [ 1, // NaN, // NaN, // NaN, // NaN ] ``` Use `unary(parseInt)` to solve the problem: ```javascript ['1', '2', '3', '4', '5'].map(unary(parseInt)) //=> [ 1, 2, 3, 4, 5 ] ``` `binary` has similar uses when working with `Array.reduce` and its habit of passing three parameters to your supplied function. ## Miscellaneous Combinators ### bound ```javascript var bound = require('allong.es').allong.es.bound; bound(fn, args...)(obj) //=> fn.bind(obj, args...) ``` ### getWith ```javascript var getWith = require('allong.es').allong.es.getWith; array.map(getWith('property')) //=> array.map(function (element) { // return element['property'] // }) ``` ## Functional Composition ```javascript var compose = require('allong.es').allong.es.compose, sequence = require('allong.es').allong.es.sequence; compose(a, b, c) //=> function (x) { // return a(b(c(x))) // } sequence(a, b, c) //=> function (x) { // return c(b(a(x))) // } ``` ## List Combinators ### mapWith and deepMapWith ```javascript var mapWith = require('allong.es').allong.es.mapWith, deepMapWith = require('allong.es').allong.es.deepMapWith; var squareList = mapWith(function (x) { return x * x }) squareList([1, 2, 3, 4]) //=> [1, 4, 9, 16] var squareTree = deepMapWith(function (x) { return x * x }) squareTree([1, 2, [3, 4]]) //=> [1, 4, [9, 16]] ``` ## Function/Method Decorators ### maybe ```javascript var maybe = require('allong.es').allong.es.maybe; var safeFirst = maybe(function (arr) { return arr[0] }) safeFirst([1, 2, 3]) //=> 1 safeFirst(null) //=> null ``` ### tap ```javascript var tap = require('allong.es').allong.es.tap; tap([1, 2, 3, 4, 5], send('pop')) //=> [1, 2, 3, 4] ``` ### fluent ```javascript var fluent = require('allong.es').allong.es.fluent; Role = function () {} Role.prototype.set = fluent( function (property, name) { this[property] = name }) var doomed = new Role() .set('name', "Fredo") .set('relationship', 'brother') .set('parts', ['I', 'II']) ``` ### once ```javascript var once = require('allong.es').allong.es.once; var message = once( function () { console.log("Hello, it's me") }) message() //=> "Hello, it's me" message() //=> message() //=> message() //=> ``` ## Decorating Classes/Constructors ```javascript var mixin = require('allong.es').allong.es.mixin, classDecorator = require('allong.es').allong.es.classDecorator; function Todo (name) { var self = this instanceof Todo ? this : new Todo(); self.name = name || 'Untitled'; self.done = false; }; Todo.prototype.do = fluent( function () { this.done = true; }); Todo.prototype.undo = fluent( function () { this.done = false; }); var AddLocation = mixin({ setLocation: fluent( function (location) { this.location = location; }), getLocation: function () { return this.location; } }); AddLocation.call(Todo.prototype); // Or use AddLocation(Todo.prototype) new Todo("Vacuum").setLocation('Home'); //=> { name: 'Vacuum', // done: false, // location: 'Home' } var AndColourCoded = classDecorator({ setColourRGB: fluent( function (r, g, b) { this.colourCode = { r: r, g: g, b: b }; }), getColourRGB: function () { return this.colourCode; } }); var ColourTodo = AndColourCoded(Todo); new ColourTodo('Use More Decorators').setColourRGB(0, 255, 0); //=> { name: 'Use More Decorators', // done: false, // colourCode: { r: 0, g: 255, b: 0 } } ``` Note: `classDecorator` works with JavaScript constructors that have a default implementation (they work properly with no arguments), and are new-agnostic (they can be called with new or as a normal function). `Todo` above has both properties. ## Functional Iterators Functional iterators are stateful functions that "iterate over" the values in some ordered data set. You call the iterator repeatedly to obtain the values, and it will either never stop returning values (an infinite data set) or return `undefined` when there are no more values to return. The functional iterators utilities are all namespaced: ```javascript var iterators = require('allong.es').allong.es.iterators; ``` ### FlatArrayIterator and RecursiveArrayIterator Making functional iterators from arrays: ```javascript var FlatArrayIterator = iterators.FlatArrayIterator, RecursiveArrayIterator = iterators.RecursiveArrayIterator; var i = FlatArrayIterator([1, 2, 3, 4, 5]); i(); //=> 1 i(); //=> 2 i(); //=> 3 i(); //=> 4 i(); //=> 5 i(); //=> undefined var i = FlatArrayIterator([1, [2, 3, 4], 5]); i(); //=> 1 i(); //=> [2, 3, 4] i(); //=> 5 i(); //=> undefined var i = RecursiveArrayIterator([1, [2, 3, 4], 5]); i(); //=> 1 i(); //=> 2 i(); //=> 3 i(); //=> 4 i(); //=> 5 i(); //=> undefined ``` ### range and numbers ```javascript var range = iterators.range, numbers = iterators.numbers; var i = range(1, 5); i(); //=> 1 i(); //=> 2 i(); //=> 3 i(); //=> 4 i(); //=> 5 i(); //=> undefined var i = range(1, 5, 2); i(); //=> 1 i(); //=> 3 i(); //=> 5 i(); //=> undefined var i = range(5, 1); i(); //=> 5 i(); //=> 4 i(); //=> 3 i(); //=> 2 i(); //=> 1 i(); //=> undefined var i = range(1); i(); //=> 1 i(); //=> 2 i(); //=> 3 // ... var i = numbers(); i(); //=> 1 i(); //=> 2 i(); //=> 3 // ... var i = numbers(0); i(); //=> 0 i(); //=> 1 i(); //=> 2 i(); //=> 3 // ... ``` ### unfold and unfoldWithReturn Unfold makes an iterator out of a seed by successively applying a function to the seed value. Here's an example duplicating the "numbers" feature: ```javascript var unfold = iterators.unfold, unfoldWithReturn = iterators.unfoldWithReturn; var i = unfold(1, function (n) { return n + 1; }); i(); //=> 1 i(); //=> 2 i(); //=> 3 // ... var i = unfoldWithReturn(1, function (n) { return [n + 1, n + n]; }); i(); //=> 2 i(); //=> 4 i(); //=> 6 // ... ``` A richer example of `unfoldWithReturn`: ```javaascript var cards = ['A', 2, 3, 4, 5, 6, 7, 8, 9, '10', 'J', 'Q', 'K']; function pickCard (deck) { var position; if (deck.length === 0) { return [[], void 0]; } else { position = Math.floor(Math.random() * deck.length); return [ deck.slice(0, position).concat(deck.slice(position + 1)), deck[position] ]; } }; var i = unfoldWithReturn(cards, pickCard); i(); //=> 5 i(); //=> 4 i(); //=> 2 i(); //=> J // ... ``` ### map Stateless mapping of an iterator to another iterator: ```javascript var map = iterators.map; var squares = map(numbers, function (n) { return n * n; }); squares(); //=> 1 squares(); //=> 4 squares(); //=> 9 // ... ``` ### accumulate Accumulating an iterator to another iterator, a/k/a stateful mapping, with an optional seed: ```javascript var accumulate = iterators.accumulate; var runningTotal = accumulate(numbers, function (accumulation, n) { return accumulation + n; }); runningTotal(); //=> 1 runningTotal(); //=> 3 runningTotal(); //=> 6 runningTotal(); //=> 10 runningTotal(); //=> 15 // ... var runningTotal = accumulate(numbers, function (accumulation, n) { return accumulation + n; }, 5); runningTotal(); //=> 6 runningTotal(); //=> 8 runningTotal(); //=> 11 runningTotal(); //=> 15 runningTotal(); //=> 20 // ... ``` ### accumulateWithReturn This code transforms filters duplicates out of an iterator of numbers by turning them into "false." It consumes space proportional to the time it runs and the size of the set of possible numbers in its iterator. ```javascript var accumulateWithReturn = iterators.accumulateWithReturn; var randomNumbers = function () { return Math.floor(Math.random() * 10); }; randomNumbers(); //=> 7 randomNumbers(); //=> 0 randomNumbers(); //=> 1 randomNumbers(); //=> 1 randomNumbers(); //=> 6 // ... var uniques = accumulateWithReturn(randomNumbers, function (alreadySeen, number) { var key = number.toString(); if (alreadySeen[key]) { return [alreadySeen, false]; } else { alreadySeen[key] = true; return [alreadySeen, number]; } }, {}); uniques(); //=> 7 uniques(); //=> 5 uniques(); //=> 1 uniques(); //=> false uniques(); //=> 9 uniques(); //=> 4 uniques(); //=> false // ... ``` ### select and reject ```javascript var select = iterators.select, reject = iterators.reject; function isEven (number) { return number === 0 || !isEven(number - 1); }; var evens = select(randomNumbers, isEven); evens(); //=> 0 evens(); //=> 6 evens(); //=> 0 evens(); //=> 2 evens(); //=> 4 // ... var odds = reject(randomNumbers, isEven); odds(); //=> 3 odds(); //=> 1 odds(); //=> 7 odds(); //=> 9 odds(); //=> 9 // ... ``` Note: `select` and `reject` will enter an "infinite loop" if the iterator does not terminate and also does not have any elements matching the condition. ### slice ```javascript var slice = iterators.slice, numbers = unfold(1, function (n) { return n + 1; }); var i = slice(numbers, 3); i(); //=> 4 i(); //=> 5 i(); //=> 6 i = slice(numbers, 3, 2); i(); //=> 10 i(); //=> 11 i(); //=> undefined ``` ### take ```javascript var take = iterators.take, numbers = unfold(1, function (n) { return n + 1; }); var i = take(numbers); i(); //=> 1 i(); //=> undefined var i = take(numbers); i(); //=> 2 i(); //=> undefined var i = take(numbers, 3); i(); //=> 3 i(); //=> 4 i(); //=> 5 i(); //=> undefined // ... ``` ### drop ```javascript var drop = iterators.drop, numbers = unfold(1, function (n) { return n + 1; }); drop(numbers); numbers(); //=> 2 numbers(); //=> 3 numbers(); //=> 4 drop(numbers); numbers(); //=> 6 numbers(); //=> 7 drop(numbers, 3); numbers(); //=> 11 numbers(); //=> 12 // ... ``` ## Trampolining ``` var trampoline = require('allong.es').allong.es.trampoline, tailCall = require('allong.es').allong.es.tailCall; function factorial (n) { var _factorial = trampoline( function myself (acc, n) { return n > 0 ? tailCall(myself, acc * n, n - 1) : acc }); return _factorial(1, n); }; factorial(10); //=> 3628800 ``` ================================================ FILE: _site/Gruntfile.coffee ================================================ module.exports = (grunt) -> grunt.initConfig pkg: grunt.file.readJSON('package.json'), uglify: options: banner: '/*! http://github.com/raganwald/<%= pkg.name %> v<%= pkg.version %> <%= grunt.template.today("yyyy-mm-dd") %> (c) 2012-2013 Reg Braithwaite MIT Licensed */\n' build: src: 'lib/<%= pkg.name %>.js', dest: 'lib/<%= pkg.name %>.min.js' grunt.loadNpmTasks('grunt-contrib-uglify') grunt.registerTask('default', ['uglify']) ================================================ FILE: _site/README.md ================================================ # `allong.es` The `allong.es` library is a collection of functions designed to facilitate writing JavaScript and/or CoffeeScript with functions as first-class values. The emphasis in `allong.es` is on composing and decomposing functions using combinators and decorators. `allong.es` is designed to complement libraries like [Underscore](http://underscorejs.org), not compete with them. ## Currying and Partial Application At the heart of `allong.es` are the functions that curry and partially apply other functions. The two most important to understand are `call` and `apply`. They work very much like the `.call` and `.apply` methods that every JavaScript function implements: ```javascript function greet (how, whom) { return '' + how + ', ' + whom + '!'; }; call(greet, 'Hello', 'Tom') //=> 'Hello, Tom!' apply(greet, ['Hello', 'Tom']) //=> 'Hello, Tom!' ``` Their "special sauce" is that they automatically *curry* the supplied function, so if you provide fewer or no arguments, you get back a partially applied or curried function: ```javascript call(greet)('Hello')('Tom') //=> 'Hello, Tom!' call(greet, 'Hello')('Tom']) //=> 'Hello, Tom!' apply(greet, [])('Hello')('Tom') //=> 'Hello, Tom!' apply(greet, ['Hello'])('Tom']) //=> 'Hello, Tom!' ``` ### immediate application If you don't want the currying/partial application behaviour, there is an immediate application version named (appropriately), `callNow` (and also another named `applyNow`, not shown): ```javascript callNow(greet, 'Hello', 'Tom') //=> 'Hello, Tom!' callNow(greet, 'Hello') //=> 'Hello, undefined!' ``` ### variations on the order of applying the arguments `callRight` applies any arguments supplied to the right. If you supply all the arguments, it's the same as `call`, but if you supply fewer arguments, you get a right partial application: ```javascript callRight(greet, 'Hello', 'Tom') //=> 'Hello, Tom!' callRight(greet, 'Hello')('Tom') //=> 'Tom, Hello!' ``` `callFlipped` applies the arguments backwards, even when curried: ```javascript callFlipped(greet, 'Hello', 'Tom') //=> 'Tom, Hello!' callFlipped(greet, 'Hello')('Tom') //=> 'Tom, Hello!' callFlipped(greet)('Hello')('Tom') //=> 'Tom, Hello!' ``` ### more partial application `callLeft` is actually synonymous with `call`: It applies arguments given to the left. We've seen `callRight` above. Both are *variadic*: You can supply as many arguments as you want. `callFirst` and `callLast` are just like `callLeft` and `callRight`, but they are *binary* functions: They accept a function and exactly one argument. This is sometimes useful when combining functions together. `callFirst` and `callLast` both have "flipped and curried" versions (`callFirstWith` and `callLastWith`). `callLastWith` is especially useful for working with functions written in "collection - operation" style. Here we take advantage of the fact that they are "automatically curried" to implement the popular `pluck` function. ### currying `allong.es` does support the `curry` function, it is implemented as the unary form of `call`: ```javascript var curry = unary(call); ``` ### with `splat` was present in earlier versions of `allong.es` but has been deprecated as being too cryptic. Instead, there is a general naming convention that works as follows. Many binary functions such as `map` and `filter` are historically written to take a noun or collection as the first argument and a verb as the second. However, reversing and currying these functions is super-useful as it makes composeable functions out of them. That's why `callFlipped` is so important. But to save you the trouble of writing `callFlipped map` everywhere, many such functions in `allong.es` have a clipped version pre-defined and named with the suffix `With`: ``` map(list, function) <=> mapWith(function, list) filter(list, function) <=> filterWith(function, list) get(object, propertyName) <=> getWith(propertyName, object) pluck(list, propertyName) <=> pluckWith(propertyName, list) ``` So you "map" a list, but "mapWith" a function. And of course, they are all curried. For example: ``` map(list)(function) <=> mapWith(function)(list) deepMap(list)(function) <=> deepMapWith(function)(list) filter(list)(function) <=> filterWith(function)(list) get(object)(propertyName) <=> getWith(propertyName)(object) pluck(list)(propertyName) <=> pluckWith(propertyName)(list) ``` Thus if you have a collection such as: ```javascript var users = [ { name: 'Huey' }, { name: 'Dewey' }, { name: 'Louie' } ] ``` You can get the names with either: ```javascript pluck(users, 'name') //=> ['Huey', 'Dewey', 'Louie'] ``` Or: ```javascript pluckWith('name', users) //=> ['Huey', 'Dewey', 'Louie'] ``` The latter is interesting because `pluck` and `pluckWith` are both automatically curried (like almost everything that isn't named "now"). Thus, we could also write: ```javascript var namesOf = pluckWith('name'); // ... namesOf(users) //=> ['Huey', 'Dewey', 'Louie'] ``` ## Arity Function Decorators ### variadic Makes a function into a variadic (accepts any number of arguments). The last named parameter will be given an array of arguments. ```javascript var variadic = require('allong.es').allong.es.variadic; var fn = variadic(function (a) { return a }) fn() //=> [] fn(1, 2, 3) //=> [1,2,3] fn = variadic(function (a,b) { return { a: a, b: b } }) fn() //=> { a: undefined, b: [] } fn(1) //=> { a: 1, b: [] } fn(1,2,3) //=> { a: 1, b: [2, 3] } ``` ### variadic, part ii When given just the function, `variadic` returns a function with an arity of zero. This is consistent with JavaScript programming practice. There are times when you wish to report an arity, meaning that you want the returned function to have its `length` getibute set. You do this by prefacing the function argument with a length: ```javascript fn = variadic(function (a,b) { return { a: a, b: b } }); fn.length //=> 0 fn2 = variadic(1, function (a,b) { return { a: a, b: b } }); fn2.length //=> 1 ``` ### unary, binary, and ternary Sometimes, you have a function that takes multiple arguments, but you only want it to accept one, or two, or maybe three arguments and ignore the rest. For example, `parseInt` takes a radix as an optional second parameter. And that is havoc if you try to use it with `Array.map`: ```javascript ['1', '2', '3', '4', '5'].map(parseInt) //=> [ 1, // NaN, // NaN, // NaN, // NaN ] ``` Use `unary(parseInt)` to solve the problem: ```javascript ['1', '2', '3', '4', '5'].map(unary(parseInt)) //=> [ 1, 2, 3, 4, 5 ] ``` `binary` has similar uses when working with `Array.reduce` and its habit of passing three parameters to your supplied function. ## Miscellaneous Combinators ### bound ```javascript var bound = require('allong.es').allong.es.bound; bound(fn, args...)(obj) //=> fn.bind(obj, args...) ``` ### getWith ```javascript var getWith = require('allong.es').allong.es.getWith; array.map(getWith('property')) //=> array.map(function (element) { // return element['property'] // }) ``` ## Functional Composition ```javascript var compose = require('allong.es').allong.es.compose, sequence = require('allong.es').allong.es.sequence; compose(a, b, c) //=> function (x) { // return a(b(c(x))) // } sequence(a, b, c) //=> function (x) { // return c(b(a(x))) // } ``` ## List Combinators ### mapWith and deepMapWith ```javascript var mapWith = require('allong.es').allong.es.mapWith, deepMapWith = require('allong.es').allong.es.deepMapWith; var squareList = mapWith(function (x) { return x * x }) squareList([1, 2, 3, 4]) //=> [1, 4, 9, 16] var squareTree = deepMapWith(function (x) { return x * x }) squareTree([1, 2, [3, 4]]) //=> [1, 4, [9, 16]] ``` ## Function/Method Decorators ### maybe ```javascript var maybe = require('allong.es').allong.es.maybe; var safeFirst = maybe(function (arr) { return arr[0] }) safeFirst([1, 2, 3]) //=> 1 safeFirst(null) //=> null ``` ### tap ```javascript var tap = require('allong.es').allong.es.tap; tap([1, 2, 3, 4, 5], send('pop')) //=> [1, 2, 3, 4] ``` ### fluent ```javascript var fluent = require('allong.es').allong.es.fluent; Role = function () {} Role.prototype.set = fluent( function (property, name) { this[property] = name }) var doomed = new Role() .set('name', "Fredo") .set('relationship', 'brother') .set('parts', ['I', 'II']) ``` ### once ```javascript var once = require('allong.es').allong.es.once; var message = once( function () { console.log("Hello, it's me") }) message() //=> "Hello, it's me" message() //=> message() //=> message() //=> ``` ## Decorating Classes/Constructors ```javascript var mixin = require('allong.es').allong.es.mixin, classDecorator = require('allong.es').allong.es.classDecorator; function Todo (name) { var self = this instanceof Todo ? this : new Todo(); self.name = name || 'Untitled'; self.done = false; }; Todo.prototype.do = fluent( function () { this.done = true; }); Todo.prototype.undo = fluent( function () { this.done = false; }); var AddLocation = mixin({ setLocation: fluent( function (location) { this.location = location; }), getLocation: function () { return this.location; } }); AddLocation.call(Todo.prototype); // Or use AddLocation(Todo.prototype) new Todo("Vacuum").setLocation('Home'); //=> { name: 'Vacuum', // done: false, // location: 'Home' } var AndColourCoded = classDecorator({ setColourRGB: fluent( function (r, g, b) { this.colourCode = { r: r, g: g, b: b }; }), getColourRGB: function () { return this.colourCode; } }); var ColourTodo = AndColourCoded(Todo); new ColourTodo('Use More Decorators').setColourRGB(0, 255, 0); //=> { name: 'Use More Decorators', // done: false, // colourCode: { r: 0, g: 255, b: 0 } } ``` Note: `classDecorator` works with JavaScript constructors that have a default implementation (they work properly with no arguments), and are new-agnostic (they can be called with new or as a normal function). `Todo` above has both properties. ## Functional Iterators Functional iterators are stateful functions that "iterate over" the values in some ordered data set. You call the iterator repeatedly to obtain the values, and it will either never stop returning values (an infinite data set) or return `undefined` when there are no more values to return. The functional iterators utilities are all namespaced: ```javascript var iterators = require('allong.es').allong.es.iterators; ``` ### FlatArrayIterator and RecursiveArrayIterator Making functional iterators from arrays: ```javascript var FlatArrayIterator = iterators.FlatArrayIterator, RecursiveArrayIterator = iterators.RecursiveArrayIterator; var i = FlatArrayIterator([1, 2, 3, 4, 5]); i(); //=> 1 i(); //=> 2 i(); //=> 3 i(); //=> 4 i(); //=> 5 i(); //=> undefined var i = FlatArrayIterator([1, [2, 3, 4], 5]); i(); //=> 1 i(); //=> [2, 3, 4] i(); //=> 5 i(); //=> undefined var i = RecursiveArrayIterator([1, [2, 3, 4], 5]); i(); //=> 1 i(); //=> 2 i(); //=> 3 i(); //=> 4 i(); //=> 5 i(); //=> undefined ``` ### range and numbers ```javascript var range = iterators.range, numbers = iterators.numbers; var i = range(1, 5); i(); //=> 1 i(); //=> 2 i(); //=> 3 i(); //=> 4 i(); //=> 5 i(); //=> undefined var i = range(1, 5, 2); i(); //=> 1 i(); //=> 3 i(); //=> 5 i(); //=> undefined var i = range(5, 1); i(); //=> 5 i(); //=> 4 i(); //=> 3 i(); //=> 2 i(); //=> 1 i(); //=> undefined var i = range(1); i(); //=> 1 i(); //=> 2 i(); //=> 3 // ... var i = numbers(); i(); //=> 1 i(); //=> 2 i(); //=> 3 // ... var i = numbers(0); i(); //=> 0 i(); //=> 1 i(); //=> 2 i(); //=> 3 // ... ``` ### unfold and unfoldWithReturn Unfold makes an iterator out of a seed by successively applying a function to the seed value. Here's an example duplicating the "numbers" feature: ```javascript var unfold = iterators.unfold, unfoldWithReturn = iterators.unfoldWithReturn; var i = unfold(1, function (n) { return n + 1; }); i(); //=> 1 i(); //=> 2 i(); //=> 3 // ... var i = unfoldWithReturn(1, function (n) { return [n + 1, n + n]; }); i(); //=> 2 i(); //=> 4 i(); //=> 6 // ... ``` A richer example of `unfoldWithReturn`: ```javaascript var cards = ['A', 2, 3, 4, 5, 6, 7, 8, 9, '10', 'J', 'Q', 'K']; function pickCard (deck) { var position; if (deck.length === 0) { return [[], void 0]; } else { position = Math.floor(Math.random() * deck.length); return [ deck.slice(0, position).concat(deck.slice(position + 1)), deck[position] ]; } }; var i = unfoldWithReturn(cards, pickCard); i(); //=> 5 i(); //=> 4 i(); //=> 2 i(); //=> J // ... ``` ### map Stateless mapping of an iterator to another iterator: ```javascript var map = iterators.map; var squares = map(numbers, function (n) { return n * n; }); squares(); //=> 1 squares(); //=> 4 squares(); //=> 9 // ... ``` ### accumulate Accumulating an iterator to another iterator, a/k/a stateful mapping, with an optional seed: ```javascript var accumulate = iterators.accumulate; var runningTotal = accumulate(numbers, function (accumulation, n) { return accumulation + n; }); runningTotal(); //=> 1 runningTotal(); //=> 3 runningTotal(); //=> 6 runningTotal(); //=> 10 runningTotal(); //=> 15 // ... var runningTotal = accumulate(numbers, function (accumulation, n) { return accumulation + n; }, 5); runningTotal(); //=> 6 runningTotal(); //=> 8 runningTotal(); //=> 11 runningTotal(); //=> 15 runningTotal(); //=> 20 // ... ``` ### accumulateWithReturn This code transforms filters duplicates out of an iterator of numbers by turning them into "false." It consumes space proportional to the time it runs and the size of the set of possible numbers in its iterator. ```javascript var accumulateWithReturn = iterators.accumulateWithReturn; var randomNumbers = function () { return Math.floor(Math.random() * 10); }; randomNumbers(); //=> 7 randomNumbers(); //=> 0 randomNumbers(); //=> 1 randomNumbers(); //=> 1 randomNumbers(); //=> 6 // ... var uniques = accumulateWithReturn(randomNumbers, function (alreadySeen, number) { var key = number.toString(); if (alreadySeen[key]) { return [alreadySeen, false]; } else { alreadySeen[key] = true; return [alreadySeen, number]; } }, {}); uniques(); //=> 7 uniques(); //=> 5 uniques(); //=> 1 uniques(); //=> false uniques(); //=> 9 uniques(); //=> 4 uniques(); //=> false // ... ``` ### select and reject ```javascript var select = iterators.select, reject = iterators.reject; function isEven (number) { return number === 0 || !isEven(number - 1); }; var evens = select(randomNumbers, isEven); evens(); //=> 0 evens(); //=> 6 evens(); //=> 0 evens(); //=> 2 evens(); //=> 4 // ... var odds = reject(randomNumbers, isEven); odds(); //=> 3 odds(); //=> 1 odds(); //=> 7 odds(); //=> 9 odds(); //=> 9 // ... ``` Note: `select` and `reject` will enter an "infinite loop" if the iterator does not terminate and also does not have any elements matching the condition. ### slice ```javascript var slice = iterators.slice, numbers = unfold(1, function (n) { return n + 1; }); var i = slice(numbers, 3); i(); //=> 4 i(); //=> 5 i(); //=> 6 i = slice(numbers, 3, 2); i(); //=> 10 i(); //=> 11 i(); //=> undefined ``` ### take ```javascript var take = iterators.take, numbers = unfold(1, function (n) { return n + 1; }); var i = take(numbers); i(); //=> 1 i(); //=> undefined var i = take(numbers); i(); //=> 2 i(); //=> undefined var i = take(numbers, 3); i(); //=> 3 i(); //=> 4 i(); //=> 5 i(); //=> undefined // ... ``` ### drop ```javascript var drop = iterators.drop, numbers = unfold(1, function (n) { return n + 1; }); drop(numbers); numbers(); //=> 2 numbers(); //=> 3 numbers(); //=> 4 drop(numbers); numbers(); //=> 6 numbers(); //=> 7 drop(numbers, 3); numbers(); //=> 11 numbers(); //=> 12 // ... ``` ## Trampolining ``` var trampoline = require('allong.es').allong.es.trampoline, tailCall = require('allong.es').allong.es.tailCall; function factorial (n) { var _factorial = trampoline( function myself (acc, n) { return n > 0 ? tailCall(myself, acc * n, n - 1) : acc }); return _factorial(1, n); }; factorial(10); //=> 3628800 ``` ================================================ FILE: _site/functions.txt ================================================ { variadic: [Function], unvariadic: [Function: unvariadic], curryWithLeftAndRight: [Function: curryWithLeftAndRight], unary: [Function: unary], binary: [Function: binary], ternary: [Function: ternary], quaternary: [Function: quaternary], selfCurrying: [Function: selfCurrying], compose: [Function], sequence: [Function], callFlipped: { [Function] unary: [Function: unary], binary: [Function: binary], ternary: [Function: ternary], quaternary: [Function: quaternary], callWithLeftFlipped: [Function: callWithLeftFlipped] }, flip: [Function: myself], applyNow: [Function: myself], callNow: { [Function] unary: [Function: unary], binary: [Function: binary], ternary: [Function: ternary], quaternary: [Function: quaternary] }, call: [Function], callLeft: [Function], callRight: [Function], applyN: [Function: myself], applyLeftNow: [Function: myself], callLeftNow: [Function], applyLeftNowWith: [Function: myself], applyRightNow: [Function: myself], callRightNow: [Function], applyRightNowWith: [Function: myself], callFirst: [Function: myself], callLast: [Function: myself], callFirstWith: [Function: myself], callLastWith: [Function: myself], bound: [Function], defaults: [Function], args: [Function: args], curry: [Function: myself], map: [Function: myself], mapWith: [Function: myself], filterWith: [Function: filterWith], deepMap: [Function: myself], deepMapWith: [Function: myself], maybe: [Function: maybe], tap: [Function: tap], fluent: [Function: fluent], returnFirst: [Function], tee: [Function: tee], once: [Function: once], memoized: [Function: memoized], mixin: [Function: mixin], classDecorator: [Function: classDecorator], bind: [Function: bind], unbind: [Function: unbind], invoke: [Function: invoke], get: [Function: get], getWith: [Function: getWith], send: [Function], pluckWith: [Function], pluck: [Function: myself], trampoline: [Function: trampoline], tailCall: [Function], Thunk: [Function: Thunk], iterators: { accumulate: [Function: accumulate], accumulateWithReturn: [Function: accumulateWithReturn], fold: [Function: fold], unfold: [Function: unfold], unfoldWithReturn: [Function: unfoldWithReturn], map: [Function: map], select: [Function: select], reject: [Function: reject], filter: [Function: select], find: [Function: find], slice: [Function: slice], drop: [Function], take: [Function: take], FlatArrayIterator: [Function: FlatArrayIterator], RecursiveArrayIterator: [Function: RecursiveArrayIterator], constant: [Function: K], K: [Function: K], numbers: [Function: myself], range: [Function: range] } } ================================================ FILE: _site/lib/allong.es.js ================================================ /*! http://github.com/raganwald/allong.es (c) 2012-2013 Reg Braithwaite MIT Licensed */ (function (root) { // Setup // ----- // Establish the root object, `window` in the browser, or `global` on the server. // *taken from [Underscore.js](http://underscorejs.org/)* var root = this; var __slice = Array.prototype.slice, __map = Array.prototype.map, __hasProp = Array.prototype.hasOwnProperty, __filter = Array.prototype.filter; // here's our export object var allong = { es: {} }; // ## Functionalizing // // The utility functions operate on other functions. They can also operate on string // abbreviations for functions by calling `functionalzie(...)` on their inputs. // SHIM if ('ab'.split(/a*/).length < 2) { if (typeof console !== "undefined" && console !== null) { console.log("Warning: IE6 split is not ECMAScript-compliant. This breaks '->1'"); } } // on the fence about whether to export this? function to_function (str) { var expr, leftSection, params, rightSection, sections, v, vars, _i, _len; params = []; expr = str; sections = expr.split(/\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 { 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 { vars = str.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 (_i = 0, _len = vars.length; _i < _len; _i++) { v = vars[_i]; params.indexOf(v) >= 0 || params.push(v); } } } return new Function(params, 'return (' + expr + ')'); }; function functionalize (fn) { if (typeof fn === 'function') { return fn; } else if (typeof fn === 'string' && /^[_a-zA-Z]\w*$/.test(fn)) { return function() { var args, receiver, _ref; receiver = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; return (_ref = receiver[fn]).call.apply(_ref, [receiver].concat(__slice.call(args))); }; } else if (typeof fn === 'string') { return to_function(fn); } else if (typeof fn.lambda === 'function') { return fn.lambda(); } else if (typeof fn.toFunction === 'function') { return fn.toFunction(); } }; function extend () { var consumer = arguments[0], providers = __slice.call(arguments, 1), key, i, provider, except; for (i = 0; i < providers.length; ++i) { provider = providers[i]; except = provider['except'] || []; except.push('except'); for (key in provider) { if (except.indexOf(key) < 0 && provider.hasOwnProperty(key)) { consumer[key] = provider[key]; }; }; }; return consumer; }; function rotate (array, n) { var copy = array.slice(0), i, pull, push; if (n !== 0) { n || (n = 1); if (n > 0) { pull = 'shift'; push = 'push'; } else { n = -n; pull = 'pop'; push = 'unshift' } for (i = 0; i < n; ++i) { copy[push](copy[pull]()); } } return copy; }; function reverse (array) { return array.reduce(function (acc, element) { acc.unshift(element); return acc; }, []); }; // # ARITY function invokeImmediately (fn) { return fn(); }; // ### "Variadic" // // fn = variadic(function (args) { return args }) // // fn() //=> [] // fn(1) //=> [1] // fn(1, 2) //=> [1, 2] // fn(1, 2, 3) //=> [1, 2, 3] // // fn = variadic(function (first, rest) { return [first, rest]}) // // fn() //=> [undefined, []] // fn(1) //=> [1, []] // fn(1, 2) //=> [1, [2]] // fn(1, 2, 3) //=> [1, [2, 3]] // // fn = variadic(function (first, second, rest) { return [first, second, rest]}) // // fn() //=> [undefined, undefined, []] // fn(1) //=> [1, undefined, []] // fn(1, 2) //=> [1, 2, []] // fn(1, 2, 3) //=> [1, 2, [3]] var variadic = function () { var FUNCTIONS = {}; function oldVariadic (fn) { var fnLength = fn.length; if (fnLength < 1) { return fn; } else if (fnLength === 1) { return function () { return fn.call(this, __slice.call(arguments, 0)) } } else { return function () { var numberOfArgs = arguments.length, namedArgs = __slice.call(arguments, 0, fnLength - 1), numberOfMissingNamedArgs = Math.max(fnLength - numberOfArgs - 1, 0), argPadding = new Array(numberOfMissingNamedArgs), variadicArgs = __slice.call(arguments, fn.length - 1); return fn.apply(this, namedArgs.concat(argPadding).concat([variadicArgs])) } } }; return function (arity, fn) { if (fn == null) { fn = functionalize(arity); arity = 0; } else fn = functionalize(fn); var fnLength = fn.length; if (arity === 0) { return oldVariadic(fn); } else if (fnLength <= arity) { var fixedParams = fnLength - 1; var index = '' + arity + '-' + fixedParams; var code; if (FUNCTIONS[index] == null) { var parameters = new Array(arity); for (var i = 0; i < arity; ++i) { parameters[i] = "__" + i; } var pstr = parameters.join(); if (fnLength > 1) { var cstr = parameters.slice(0, fnLength - 1).join(); code = "return function ("+pstr+") { return fn.call("+cstr+", [].slice.call(arguments,"+(fnLength - 1)+")); };"; } else code = "return function ("+pstr+") { return fn.call(this, [].slice.call(arguments, 0)); };"; FUNCTIONS[index] = new Function(['fn'], code); } return FUNCTIONS[index](fn); } else throw 'not supported yet' }; }(); // sets a fixed arity for a function, without currying var unvariadic = (function () { var FUNCTIONS = {}; return function unvariadic (arity, fn) { if (FUNCTIONS[arity] == null) { var parameters = new Array(arity); for (var i = 0; i < arity; ++i) { parameters[i] = "__" + i; } var pstr = parameters.join(); var code = "return function ("+pstr+") { return fn.apply(this, arguments); };"; FUNCTIONS[arity] = new Function(['fn'], code); } if (fn == null) { return function (fn) { return unvariadic(arity, fn); }; } else return FUNCTIONS[arity](functionalize(fn)); }; })(); // a kind of optional semantics: unary(f)(value) === f(value), unary(f)() === unary(f) function unary (fn) { return function unary (a) { if (a == null) { return unary; } else return fn(a); } }; function binary (fn) { return function binary (a, b) { if (a == null) { return binary; } else if (b == null) { return unary(function (b) { return fn(a, b); }); } else return fn(a, b); } }; function ternary (fn) { return function ternary (a, b, c) { if (a == null) { return ternary; } else if (b == null) { return binary(function (b, c) { return fn(a, b, c); }); } else if (c == null) { return unary(function (c) { return fn(a, b, c); }); } else return fn(a, b, c); } }; function quaternary (fn) { return function quaternary (a, b, c, d) { if (a == null) { return quaternary; } else if (b == null) { return ternary(function (b, c, d) { return fn(a, b, c, d); }); } else if (c == null) { return binary(function (c, d) { return fn(a, b, c, d); }); } else if (d == null) { return unary(function (d) { return fn(a, b, c, d); }); } else return fn(a, b, c, d); } }; var byArity = [ invokeImmediately, unary, binary, ternary, quaternary ], byArityLength = byArity.length; function curryWithLeftAndRight (fn, leftArgs, rightArgs) { leftArgs || (leftArgs = []); rightArgs || (rightArgs = []); var fnLength = fn.length, remainingLength = fnLength - leftArgs.length - rightArgs.length; if (remainingLength < byArityLength) { return byArity[remainingLength](handleRemaining); } else return unvariadic(remainingLength, handleRemaining); function handleRemaining () { var params = __slice.call(arguments, 0, arguments.length), numParams = ((params.indexOf(void 0) >= 0) ? params.indexOf(void 0) : params.length), newLeft = leftArgs.concat(params.slice(0, numParams)), args = newLeft.concat(rightArgs), argsLength = args.length, remainingLength = fnLength - argsLength; if (remainingLength <= 0) { return fn.apply(this, args); } else return curryWithLeftAndRight(fn, newLeft, rightArgs); }; }; extend(allong.es, { curryWithLeftAndRight: curryWithLeftAndRight }); // TODO: Deprecate function selfCurrying(fn) { fn = functionalize(fn); var fnLength = fn.length; if (fn.length > 0) { return variadic(fnLength, function (args) { if (args.length === 0) { return curry(fn) } else return fn.apply(this, args); }); } else return fn; }; extend(allong.es, { variadic: variadic, unvariadic: unvariadic, unary: unary, binary: binary, ternary: ternary, quaternary: quaternary }); // ### Composition // compose(a, b, c) // //=> function (x) { // return a(b(c(x))) // } var compose = variadic( function compose (fns) { fns = fns.map(functionalize); var first, firstLength, second, rest; if (fns.length === 0) { return function () {}; } else if (fns.length === 1) { return fns[0]; } else if (fns.length === 2) { first = fns[0]; firstLength = first.length; second = fns[1]; if (firstLength === 1) { return function (a) { return first(second(a)); }; } else if (firstLength === 2) { return function (a, b) { return first(second(a), b); }; } else if (firstLength === 3) { return function (a, b, c) { return first(second(a), b, c); }; } else return variadic( function (a, rest) { first.apply(this, [second(a)].concat(rest)) }); } else { var first = fns[0], butFirst = __slice.call(fns, 1); return compose.call(this, first, compose.apply(this, butFirst)); } }); extend(allong.es, { compose: compose }); // # CALL_FLIPPED var callFlipped = (function () { function nullary (fn) { return variadic( function (args) { return fn.apply(this, reverse(args)); }); }; // a kind of optional semantics: unary(f)(value) === f(value), unary(f)() === unary(f) function unary (fn) { return function unary (a) { if (a == null) { return unary; } else return fn(a); } }; function binary (fn) { return function binary (a, b) { if (a == null) { return binary; } else if (b == null) { return unary(function (b) { return fn(b, a); }); } else return fn(b, a); } }; function ternary (fn) { return function ternary (a, b, c) { if (a == null) { return ternary; } else if (b == null) { return binary(function (c, b) { return fn(c, b, a); }); } else if (c == null) { return unary(function (c) { return fn(c, b, a); }); } else return fn(c, b, a); } }; function quaternary (fn) { return function quaternary (a, b, c, d) { if (a == null) { return quaternary; } else if (b == null) { return ternary(function (d, c, b) { return fn(d, c, b, a); }); } else if (c == null) { return binary(function (d, c) { return fn(d, c, b, a); }); } else if (d == null) { return unary(function (d) { return fn(d, c, b, a); }); } else return fn(d, c, b, a); } }; var byArity = [ nullary, unary, binary, ternary, quaternary ], byArityLength = byArity.length; function callWithLeftFlipped (fn, leftArgs) { leftArgs || ( leftArgs = []); var fnLength = fn.length, remainingLength = fnLength - leftArgs.length; if (remainingLength < byArityLength) { return byArity[remainingLength](handleRemaining); } else return unvariadic(remainingLength, handleRemaining); function handleRemaining () { var params = __slice.call(arguments, 0, arguments.length), numParams = (params.indexOf(void 0) >= 0) ? params.indexOf(void 0) : params.length args = leftArgs.concat(params.slice(0, numParams)); argsLength = args.length, remainingLength = fnLength - argsLength; if (remainingLength <= 0) { return fn.apply(this, reverse(args)); } else return callWithLeftFlipped(fn, args); }; }; return extend( variadic( function callFlipped (fn, args) { fn = functionalize(fn); var fnLength = fn.length, flipped = (fnLength < byArityLength) ? byArity[fnLength](fn) : callWithLeftFlipped(fn); if (args.length === 0) { return flipped; } else return flipped.apply(this, args); }), { unary: unary, binary: binary, ternary: ternary, quaternary: quaternary, callWithLeftFlipped: callWithLeftFlipped }); })(); // synonymish var flip = unary(callFlipped); extend(allong.es, { callFlipped: callFlipped, flip: flip }); // # APPLY // the basics: apply and call // call that retuns a curried function var apply = binary( function (fn, args) { return curryWithLeftAndRight(fn, args, []); }); var call = variadic( function (fn, args) { return curryWithLeftAndRight(fn, args, []); }); var curry = unary(call); function urApply (fn, args) { return fn.apply(this, args); }; var applyNow = call( urApply ); var callNow = extend( variadic(applyNow), { unary: unary, binary: binary, ternary: ternary, quaternary: quaternary }); // flipped forms var applyNowFlipped = flip(urApply); // apply and call left function urApplyLeft (fn, leftArgs) { var remainingLength = fn.length - leftArgs.length; if (fn.length > 0 && remainingLength > 0) { return variadic(remainingLength, function (args) { return fn.apply(this, leftArgs.concat(args)); }); } else return fn.apply(this, leftArgs); }; var applyLeftNow = call(urApplyLeft); var callLeftNow = variadic(urApplyLeft); // flipped var applyLeftNowWith = flip(urApplyLeft); var applyRightNowWith = flip(urApplyRight); // rightmost function urApplyRight (fn, rightArgs) { var remainingLength = fn.length - rightArgs.length; if (fn.length > 0 && remainingLength > 0) { return variadic(remainingLength, function (args) { return fn.apply(this, args.concat(rightArgs)); }); } else return fn.apply(this, rightArgs); } var applyRightNow = call(urApplyRight); var callRightNow = variadic(urApplyRight); var callRight = variadic( function (fn, args) { return curryWithLeftAndRight(fn, [], args); }); var callFirst = binary(call); var callFirstWith = flip(callFirst); var callLast = binary(callRight); var callLastWith = flip(callLast); // ### Partial applications that bind // A partially applied binding function // // roughly equivalent to applyRight // // var fn = function (...) { ... } // // bound(fn)(x) // //=> fn.bind(x) // // bound(fn, foo)(x) // //=> fn.bind(x, foo) // // bound(fn, foo, bar)(x) // //=> fn.bind(x, foo, bar) var bound = variadic( function (messageName, args) { if (args === []) { return function (instance) { return instance[messageName].bind(instance) } } else { return function (instance) { return Function.prototype.bind.apply( instance[messageName], [instance].concat(args) ) } } }); var defaults = variadic( function (fn, values) { var fln = fn.length, vln = values.length; return variadic( function (args) { var aln = args.length, mln = Math.max(fln - aln, 0); args = args.concat(values.slice(vln-mln)); return fn.apply(this, args); }) }); // collects arguments function args (arity) { if (arity === 1) { return function (a) { return [a]; }; } else if (arity === 2) { return function (a, b) { return [a, b]; }; } else if (arity === 3) { return function (a, b, c) { return [a, b, c]; }; } else return variadic( function (args) { return args; }); }; extend(allong.es, { applyNow: applyNow, apply: apply, callNow: callNow, call: call, callLeft: call, callRight: callRight, applyNowFlipped: applyNowFlipped, applyLeftNow: applyLeftNow, callLeftNow: callLeftNow, applyLeftNowWith: applyLeftNowWith, applyRightNow: applyRightNow, callRightNow: callRightNow, applyRightNowWith: applyRightNowWith, callFirst: callFirst, callLast: callLast, callFirstWith: callFirstWith, callLastWith: callLastWith, bound: bound, defaults: defaults, args: args, curry: curry }); // # FOLDING var filter = binary( function filter (list, fn) { fn = functionalize(fn); return __filter.call(list, fn); }); var filterWith = flip(filter); var map = binary( function map (list, fn) { fn = functionalize(fn); var fnLength = fn.length; if (fnLength !== 1) { fn = unary(fn); } return __map.call(list, fn); }); var mapWith = flip(map); // turns any function into a recursive mapper // // deepMap(function (x) { return x * x })([1, [2, 3], 4]) // //=> [1, [4, 9], 16] function deepMap (fn) { return function innerDeepMap (tree) { return __map.call(tree, function (element) { if (Array.isArray(element)) { return innerSoak(element); } else return fn(element); }); }; }; var deepMap = binary( function (tree, fn) { fn = functionalize(fn); return __map.call(tree, function (element) { if (Array.isArray(element)) { return deepMap(element, fn); } else return fn(element); }); }); var deepMapWith = flip(deepMap); extend(allong.es, { map: map, mapWith: mapWith, filter: filter, filterWith: filterWith, deepMap: deepMap, deepMapWith: deepMapWith }); // # DECORATORS function maybe (fn) { fn = functionalize(fn); return function () { var i; if (arguments.length === 0) { return } else { for (i = 0; i < arguments.length; ++i) { if (arguments[i] == null) return } return fn.apply(this, arguments) } } } function tap (value, fn) { fn = functionalize(fn); if (fn === void 0) { return curried } else return curried(fn); function curried (fn) { if (typeof(fn) === 'function') { fn(value) } return value } } function fluent (fn) { fn = functionalize(fn); return function () { fn.apply(this, arguments); return this } } // decorates a function to return its first argument var returnFirst = function (fn) { fn = functionalize(fn); return function () { fn.apply(this, arguments); return arguments[0]; } } function tee (decoration) { decoration = functionalize(decoration); return function (fn) { fn = functionalize(fn); return compose(returnFirst(decoration), fn); }; }; function once (fn) { fn = functionalize(fn); var done = false, testAndSet; if (!!fn.name) { testAndSet = function () { this["__once__"] || (this["__once__"] = {}) if (this["__once__"][fn.name]) return true; this["__once__"][fn.name] = true; return false } } else { testAndSet = function (fn) { if (done) return true; done = true; return false } } return function () { return testAndSet.call(this) ? void 0 : fn.apply(this, arguments) } } function memoized (fn, keymaker) { fn = functionalize(fn); var lookupTable = {}, key, value; keymaker || (keymaker = function (args) { return JSON.stringify(args) }); return function () { var key = keymaker.call(this, arguments); return lookupTable[key] || ( lookupTable[key] = fn.apply(this, arguments) ) } }; extend(allong.es, { maybe: maybe, tap: tap, fluent: fluent, returnFirst: returnFirst, tee: tee, once: once, memoized: memoized }); // # MIXIN function mixin (decoration) { return function decorator () { if (arguments[0] !== void 0) { return decorator.call(arguments[0]); } else { extend(this, decoration); return this; } }; }; function classDecorator (decoration) { return function (clazz) { function Decorated () { var self = this instanceof Decorated ? this : new Decorated(); return clazz.apply(self, arguments); }; Decorated.prototype = extend(new clazz(), decoration); return Decorated; }; }; extend(allong.es, { mixin: mixin, classDecorator: classDecorator }); // # MORE var unbind = function unbind (fn) { fn = functionalize(fn); return fn.unbound ? unbind(fn.unbound()) : fn }; function bind (fn, context, force) { fn = functionalize(fn); var unbound, bound; if (force) { fn = unbind(fn) } bound = function () { return fn.apply(context, arguments) }; bound.unbound = function () { return fn; }; return bound; } function invoke (fn) { fn = functionalize(fn); var args = __slice.call(arguments, 1); return function (instance) { return fn.apply(instance, args) } }; function get (object, getName) { if (getName == null) { return function (getName) { return object[getName]; }; } else return object[getName]; }; function getWith (getName, object) { if (object == null) { return function (object) { return object[getName]; }; } else return object[getName]; }; var pluckWith = compose(mapWith, getWith), pluck = flip(pluckWith); // Send a message/invoke a method on the receiver. // TODO: Think about what it has in common with callLeft var send = variadic( function (methodName, args) { return variadic( function (receiver, remainingArgs) { var fn = receiver[methodName]; return fn.apply(receiver, args.concat(remainingArgs)) }) }); extend(allong.es, { bind: bind, unbind: unbind, invoke: invoke, get: get, getWith: getWith, send: send, pluckWith: pluckWith, pluck: pluck }); // # TRAMPOLINE function Thunk (closure) { if (!(this instanceof Thunk)) return new Thunk(closure); this.closure = closure; }; Thunk.prototype.force = function () { return this.closure(); }; function trampoline (fn) { var trampolined = variadic( function (args) { var result = fn.apply(this, args); while (result instanceof Thunk) { result = result.force(); } return result; }); trampolined.__trampolined_fn = fn; return trampolined; }; var tailCall = variadic( function (fn, args) { var context = this; if (fn.__trampolined_fn instanceof Function) { return new Thunk( function () { return fn.__trampolined_fn.apply(context, args); }); } else return new Thunk( function () { return fn.apply(context, args); }); }); extend(allong.es, { trampoline: trampoline, tailCall: tailCall, Thunk: Thunk }); // # ITERATORS (function (allong) { function fold (iter, binaryFn, seed) { var state, element; binaryFn = functionalize(binaryFn); if (seed !== void 0) { state = seed; } else { state = iter(); } element = iter(); while (element != null) { state = binaryFn.call(element, state, element); element = iter(); } return state; }; var HASNTBEENRUN = {}; function unfold (seed, unaryFn) { var state = HASNTBEENRUN; unaryFn = functionalize(unaryFn); return function () { if (state === HASNTBEENRUN) { return (state = seed); } else if (state != null) { return (state = unaryFn.call(state, state)); } else return state; }; }; // note that the unfoldWithReturn behaves differently than // unfold with respect to the first value returned function unfoldWithReturn (seed, unaryFn) { var state = seed, pair, value; unaryFn = functionalize(unaryFn); return function () { if (state != null) { pair = unaryFn.call(state, state); value = pair[1]; state = value != null ? pair[0] : void 0 return value; } else return void 0; }; }; function accumulate (iter, binaryFn, initial) { var state = initial; binaryFn = functionalize(binaryFn); return function () { element = iter(); if (element == null) { return element; } else { if (state === void 0) { return (state = element); } else return (state = binaryFn.call(element, state, element)); } } }; function accumulateWithReturn (iter, binaryFn, initial) { var state = initial, stateAndReturnValue; binaryFn = functionalize(binaryFn); return function () { element = iter(); if (element == null) { return element; } else { if (state === void 0) { return (state = element); } else { stateAndReturnValue = binaryFn.call(element, state, element); state = stateAndReturnValue[0]; return stateAndReturnValue[1]; } } } }; function map (iter, unaryFn) { unaryFn = functionalize(unaryFn); return function() { var element; element = iter(); if (element != null) { return unaryFn.call(element, element); } else { return void 0; } }; }; function select (iter, unaryPredicateFn) { unaryPredicateFn = functionalize(unaryPredicateFn); return function() { var element; element = iter(); while (element != null) { if (unaryPredicateFn.call(element, element)) { return element; } element = iter(); } return void 0; }; }; function reject (iter, unaryPredicateFn) { unaryPredicateFn = functionalize(unaryPredicateFn); return select(iter, function (something) { return !unaryPredicateFn(something); }); }; function find (iter, unaryPredicateFn) { unaryPredicateFn = functionalize(unaryPredicateFn); return select(iter, unaryPredicateFn)(); } function slice (iter, numberToDrop, numberToTake) { var count = 0; while (numberToDrop-- > 0) { iter(); } if (numberToTake != null) { return function() { if (++count <= numberToTake) { return iter(); } else { return void 0; } }; } else return iter; }; var drop = defaults(binary(slice), 1); function take (iter, numberToTake) { return slice(iter, 0, numberToTake == null ? 1 : numberToTake); } function FlatArrayIterator (array) { var index = 0; return function() { return array[index++]; }; }; function RecursiveArrayIterator (array) { var index, myself, state; index = 0; state = []; myself = function() { var element, tempState; element = array[index++]; if (element instanceof Array) { state.push({ array: array, index: index }); array = element; index = 0; return myself(); } else if (element === void 0) { if (state.length > 0) { tempState = state.pop(), array = tempState.array, index = tempState.index; return myself(); } else { return void 0; } } else { return element; } }; return myself; }; function K (value) { return function () { return value; }; }; function upRange (from, to, by) { return function () { var was; if (from > to) { return void 0; } else { was = from; from = from + by; return was; } } }; function downRange (from, to, by) { return function () { var was; if (from < to) { return void 0; } else { was = from; from = from - by; return was; } } }; function range (from, to, by) { if (from == null) { return upRange(1, Infinity, 1); } else if (to == null) { return upRange(from, Infinity, 1); } else if (by == null) { if (from <= to) { return upRange(from, to, 1); } else return downRange(from, to, 1) } else if (by > 0) { return upRange(from, to, by); } else if (by < 0) { return downRange(from, to, Math.abs(by)) } else return k(from); }; var numbers = unary(range); extend(allong.es, { iterators: { accumulate: accumulate, accumulateWithReturn: accumulateWithReturn, fold: fold, unfold: unfold, unfoldWithReturn: unfoldWithReturn, map: map, select: select, reject: reject, filter: select, find: find, slice: slice, drop: drop, take: take, FlatArrayIterator: FlatArrayIterator, RecursiveArrayIterator: RecursiveArrayIterator, constant: K, K: K, numbers: numbers, range: range }}); })(allong); // Monadic Sequencing // ------------------ // sequence(a, b, c) // //=> function (x) { // return c(b(a(x))) // } var sequence = callFlipped(compose); var Promise = require('promise'); var Supervisor = (function() { function Supervisor(methods) { var body, name; for (name in methods) { if (!__hasProp.call(methods, name)) continue; body = methods[name]; this[name] = body; } this.of || (this.of = function(value) { return value; }); this.map || (this.map = function(fn) { return fn; }); this.chain || (this.chain = function(mValue, fn) { return this.map(fn)(mValue); }); for (name in this) { if (!__hasProp.call(this, name)) continue; body = this[name]; this[name] = body.bind(this); } } return Supervisor; })(); Supervisor.Identity = new Supervisor(); Supervisor.Maybe = new Supervisor({ map: function(fn) { return function(mValue) { if (mValue === null || mValue === void 0) { return mValue; } else { return fn(mValue); } }; } }); Supervisor.Writer = new Supervisor({ of: function(value) { return [value, '']; }, map: function(fn) { return function(_arg) { var newlyWritten, result, value, writtenSoFar, _ref; value = _arg[0], writtenSoFar = _arg[1]; _ref = fn(value), result = _ref[0], newlyWritten = _ref[1]; return [result, writtenSoFar + newlyWritten]; }; } }); Supervisor.List = new Supervisor({ of: function(value) { return [value]; }, join: function(mValue) { return mValue.reduce(this.concat, this.zero()); }, map: function(fn) { return function(mValue) { return mValue.map(fn); }; }, zero: function() { return []; }, concat: function(ma, mb) { return ma.concat(mb); }, chain: function(mValue, fn) { return this.join(this.map(fn)(mValue)); } }); Supervisor.Promise = new Supervisor({ of: function(value) { return new Promise(function(resolve, reject) { return resolve(value); }); }, map: function(fnReturningAPromise) { return function(promiseIn) { return new Promise(function(resolvePromiseOut, rejectPromiseOut) { return promiseIn.then((function(value) { return fnReturningAPromise(value).then(resolvePromiseOut, rejectPromiseOut); }), rejectPromiseOut); }); }; } }); Supervisor.Callback = new Supervisor({ of: function(value) { return function(callback) { return callback(value); }; }, map: function(fn) { return function(value) { return function(callback) { return fn(value, callback); }; }; }, chain: function(mValue, fn) { var _this = this; return function(callback) { return mValue(function(value) { return _this.map(fn)(value)(callback); }); }; } }); Supervisor.sequence = function() { var args, fns, supervisor; args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; if (args[0] instanceof Supervisor) { supervisor = args[0], fns = 2 <= args.length ? __slice.call(args, 1) : []; } else { supervisor = Supervisor.Identity; fns = args; } return function() { return fns.reduce(supervisor.chain, supervisor.of.apply(supervisor, arguments)); }; }; extend(allong.es, { sequence: sequence, Supervisor: Supervisor }); // Exports and sundries // -------------------- if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = allong; } exports.allong = allong; } else { root.allong = allong; } }).call(this); ================================================ FILE: _site/package.json ================================================ { "author": "Reg Braithwaite (http://braythwayt.com)", "name": "allong.es", "description": "Combinators and Function Decorators", "version": "0.10.2", "homepage": "http://allong.es", "repository": { "type": "git", "url": "git://github.com/raganwald/allong.es.git" }, "main": "lib/allong.es.js", "scripts": { "test": "jasmine-node --coffee --verbose spec" }, "engines": { "node": "" }, "dependencies": { "promise": "" }, "devDependencies": { "jasmine-node": "", "coffee-script": "", "grunt": "~0.4.1", "grunt-contrib-uglify": "~0.2.0" } } ================================================ FILE: _site/release_notes.md ================================================ # Release Notes 1. Currying is implicit 2. "With" and "This" naming conventions, e.g. `pluckWith` is the should of `pluck` 3. `splat` is now `mapWith` 4. `applyLeft` and `applyFirst` are now `callLeft` and `callFirst` 5. `curry` now falls out of `apply`/`applyLeft`! 6. soak->deepMap ================================================ FILE: _site/spec/apply.spec.coffee ================================================ { callRight, applyNow, callNow, applyNowFlipped, call, applyLeftNow, callLeftNow, args, applyLeftNowWith, applyRightNow, callRightNow, applyRightNowWith, callFirst, callFirstWith, callLast, callLastWith, apply } = require('../lib/allong.es.js').allong.es echo = (a, b, c) -> "#{a} #{b} #{c}" five = (a, b, c, d, e) -> [a, b, c, d, e] three = (a, b, c) -> [a, b, c] twelve = (a, b, c, d, e, f, g, h, i, j, k, l) -> vari = (args...) -> args one = (x) -> x describe "apply", -> it "should apply an array of arguments to a function", -> expect( apply(three, [1, 2, 3]) ).toEqual three(1, 2, 3) it "should be curried", -> expect( apply(three)([1, 2, 3]) ).toEqual three(1, 2, 3) it "should be self-currying, it should apply what it gets", -> expect( apply(three, [1, 2])(3) ).toEqual three(1, 2, 3) describe "applyNow", -> it "should apply an array of arguments to a function", -> expect( applyNow(three, [1, 2, 3]) ).toEqual three(1, 2, 3) it "should not be self-currying, it should apply what it gets", -> expect( applyNow(three, [1, 2]) ).toEqual three(1, 2) expect( applyNow(three, [1]) ).toEqual three(1) it "should be curried", -> expect( applyNow(three)([1, 2, 3]) ).toEqual three(1, 2, 3) describe "when flipped", -> it "should apply an array of arguments to a function", -> expect( applyNowFlipped([1, 2, 3], three) ).toEqual three(1, 2, 3) it "should be curried", -> expect( applyNowFlipped([1, 2, 3])(three) ).toEqual three(1, 2, 3) describe "callNow", -> it "should apply arguments to a function", -> expect( callNow(three, 1, 2, 3) ).toEqual three(1, 2, 3) it "should not be self-currying, it should apply what it gets", -> expect( callNow(three, 1, 2) ).toEqual three(1, 2) expect( callNow(three, 1) ).toEqual three(1) it "should not be curried", -> expect( callNow(three) ).toEqual three() # variadic functions do not have a 'this' predefined at this point. describe "applyLeftNow", -> it 'should apply all the arguments if possible', -> expect( applyLeftNow(three, [1, 2, 3]) ).toEqual three(1, 2, 3) it 'should not be full application', -> expect( applyLeftNow(three, [1, 2]) ).not.toEqual three(1, 2) expect( applyLeftNow(three, [1, 2])(3) ).toEqual three(1, 2, 3) it 'should not be fully curried', -> expect( applyLeftNow(three, [1])(2) ).toEqual three(1, 2) describe "when flipped", -> it 'should apply all the arguments if possible', -> expect( applyLeftNowWith([1, 2, 3], three) ).toEqual three(1, 2, 3) it 'should not be full application', -> expect( applyLeftNowWith([1, 2], three) ).not.toEqual three(1, 2) expect( applyLeftNowWith([1, 2], three)(3) ).toEqual three(1, 2, 3) it 'should not be fully curried', -> expect( applyLeftNowWith([1], three)(2) ).toEqual three(1, 2) describe "callLeftNow", -> it 'should apply all the arguments if possible', -> expect( callLeftNow(three, 1, 2, 3) ).toEqual three(1, 2, 3) it 'should not be full application', -> expect( callLeftNow(three, 1, 2) ).not.toEqual three(1, 2) expect( callLeftNow(three, 1, 2)(3) ).toEqual three(1, 2, 3) it 'should not be fully curried', -> expect( callLeftNow(three, 1)(2) ).toEqual three(1, 2) describe "applyRightNow", -> it 'should apply all the arguments if possible', -> expect( applyRightNow(three, [1, 2, 3]) ).toEqual three(1, 2, 3) it 'should not be full application', -> expect( applyRightNow(three, [1, 2]) ).not.toEqual three(1, 2) expect( applyRightNow(three, [1, 2])(3) ).toEqual three(3, 1, 2) it 'should not be fully curried', -> expect( applyRightNow(three, [1])(2) ).toEqual three(2, 1) describe "when flipped", -> it 'should apply all the arguments if possible', -> expect( applyRightNowWith([1, 2, 3], three) ).toEqual three(1, 2, 3) it 'should not be full application', -> expect( applyRightNowWith([1, 2], three) ).not.toEqual three(1, 2) expect( applyRightNowWith([1, 2], three)(3) ).toEqual three(3, 1, 2) it 'should not be fully curried', -> expect( applyRightNowWith([1], three)(2) ).toEqual three(2, 1) describe "callRightNow", -> it 'should apply all the arguments if possible', -> expect( callRightNow(three, 1, 2, 3) ).toEqual three(1, 2, 3) it 'should not be full application', -> expect( callRightNow(three, 1, 2) ).not.toEqual three(1, 2) expect( callRightNow(three, 1, 2)(3) ).toEqual three(3, 1, 2) it 'should not be fully curried', -> expect( callRightNow(three, 1)(2) ).toEqual three(2, 1) ################################ describe "call", -> it "should call an array of arguments to a function", -> expect( call(echo, 1, 2, 3) ).toEqual "1 2 3" it "should have a curried nature", -> expect( call(five)(1, 2, 3, 4, 5) ).toEqual [1..5] expect( call(five)(1, 2, 3)(4, 5) ).toEqual [1..5] expect( call(five, 1, 2, 3)(4, 5) ).toEqual [1..5] expect( call(five, 1, 2, 3)(4)(5) ).toEqual [1..5] it "should get the arity right for small amounts", -> expect( call(five, 1, 2).length ).toEqual 3 describe "callRight", -> it "should call an array of arguments to a function", -> expect( callRight(echo, 1, 2, 3) ).toEqual "1 2 3" it "should have a curried nature", -> expect( callRight(five)(1, 2, 3, 4, 5) ).toEqual [1..5] expect( callRight(five)(1, 2, 3)(4, 5) ).toEqual [1..5] it "should apply given arguments to the right", -> expect( callRight(five, 1)(2, 3, 4, 5) ).toEqual [2, 3, 4, 5, 1] expect( callRight(five, 1, 2)(3, 4, 5) ).toEqual [3, 4, 5, 1, 2] expect( callRight(five, 1, 2, 3)(4, 5) ).toEqual [4, 5, 1, 2, 3] expect( callRight(five, 1, 2, 3, 4)(5) ).toEqual [5, 1, 2, 3, 4] expect( callRight(five, 1, 2, 3)(4, 5) ).toEqual [4, 5, 1, 2, 3] expect( callRight(five, 1, 2, 3)(4)(5) ).toEqual [4, 5, 1, 2, 3] it "should get the arity right for small amounts", -> expect( callRight(five, 1, 2).length ).toEqual 3 describe 'callFirst', -> it 'should call with the first argument', -> expect( callFirst(three, 1)(2, 3) ).toEqual [1..3] it 'should get the arity right', -> expect( callFirst.length ).toEqual 2 expect( callFirst(three, 1).length ).toEqual 2 it 'should be curried', -> expect( callFirst(three)(1)(2, 3) ).toEqual [1..3] describe 'callFirstWith', -> it 'should call with the first argument', -> expect( callFirstWith(1, three)(2, 3) ).toEqual [1..3] it 'should get the arity right', -> expect( callFirstWith.length ).toEqual 2 expect( callFirstWith(1, three).length ).toEqual 2 it 'should be curried', -> expect( callFirstWith(1)(three)(2, 3) ).toEqual [1..3] describe "args", -> it "should collect arguments into an array", -> expect( args(3)(1, 2, 3) ).toEqual [1, 2, 3] ================================================ FILE: _site/spec/arity.spec.coffee ================================================ {unvariadic, variadic, curry} = require('../lib/allong.es.js').allong.es numberOfArgs = -> arguments.length threeArguments = (a, b, c) -> arguments.length describe "unvariadic", -> it "should clip arguments", -> expect( unvariadic(3, numberOfArgs)(1, 2, 3, 4, 5) ).toEqual threeArguments(1, 2, 3, 4, 5) it "shouldn't pad arguments", -> expect( unvariadic(3, numberOfArgs)(1) ).toEqual threeArguments(1) describe "variadic", -> describe "with an arity", -> one = (args) -> [args] two = (arg, args) -> [arg, args] onev = (args...) -> [args] twov = (arg, args...) -> [arg, args] it 'should 1', -> expect( variadic(1, one)(1, 2, 3) ).toEqual onev(1, 2, 3) it 'should 2', -> expect( variadic(1, one).length ).toEqual 1 it 'should 3', -> expect( variadic(2, one).length ).toEqual 2 describe "curry", -> three = (a, b, c) -> [a, b, c] it "should be a function that returns a function", -> expect( curry instanceof Function).toEqual true expect( curry(three) instanceof Function).toEqual true it "should allow a full invocation", -> expect( curry(three)(1, 2, 3) ).toEqual [1, 2, 3] it "should allow partial invocation", -> expect( curry(three)(1)(2, 3) ).toEqual [1, 2, 3] expect( curry(three)()(1, 2, 3) ).toEqual [1, 2, 3] expect( curry(three)(1)(2)(3) ).toEqual [1, 2, 3] expect( curry(three)(1, 2)(3) ).toEqual [1, 2, 3] # describe "self-currying", -> # # it "shouldn't affect normal useage", -> # expect(selfCurrying(three)(4, 5, 6)).toEqual three(4, 5, 6) # expect(selfCurrying(three)(4, 5)).toEqual three(4, 5) # expect(selfCurrying(three)(4)).toEqual three(4) # # it "should curry with no arguments", -> # expect( selfCurrying(three)()(1)(2, 3) ).toEqual [1, 2, 3] # expect( selfCurrying(three)()()(1, 2, 3) ).toEqual [1, 2, 3] # expect( selfCurrying(three)()(1)(2)(3) ).toEqual [1, 2, 3] # expect( selfCurrying(three)()(1, 2)(3) ).toEqual [1, 2, 3] ================================================ FILE: _site/spec/basic.spec.coffee ================================================ {Sequence, Sequence: {sequence}} = require('../lib/allong.es.js').allong.es double = (n) -> n + n plusOne = (n) -> n + 1 describe "sequence", -> it "should be a thing", -> expect( sequence ).not.toBeNull() it "should return a function when given a function", -> expect( sequence(double) ).not.toBeNull() it "should sequence a single function", -> expect( sequence(double)(3) ).toEqual 6 it "should sequence two functions", -> expect( sequence(double, plusOne)(3) ).toEqual 7 describe "Identity", -> it "should sequence a single function", -> expect( sequence(Sequence.Identity, double)(3) ).toEqual 6 it "should sequence two functions", -> expect( sequence(Sequence.Identity, double, plusOne)(3) ).toEqual 7 describe "Maybe", -> it "should pass numbers through", -> expect( sequence(Sequence.Maybe, double, plusOne)(3) ).toEqual 7 it "should pass null through", -> expect( sequence(Sequence.Maybe, double, plusOne)(null) ).toBeNull() it "should pass undefined through", -> expect( sequence(Sequence.Maybe, double, plusOne)(undefined) ).toBeUndefined() it "should short-circuit", -> expect( sequence(Sequence.Maybe, double, ((x) ->), plusOne)(undefined) ).toBeUndefined() describe "Writer", -> parity = (n) -> [ n if n % 2 is 0 then 'even' else 'odd' ] space = (n) -> [ n ' ' ] size = (n) -> [ n if n < 10 then 'small' else 'normal' ] it "should accumulate writes", -> expect( sequence(Sequence.Writer, parity, space, size)(5) ).toEqual [5, 'odd small'] describe 'List', -> oneToN = (n) -> [1..n] nToOne = (n) -> [n..1] it "should handle two levels of lists", -> expect( sequence(Sequence.List, oneToN, nToOne)(3) ).toEqual [1, 2, 1, 3, 2, 1] ================================================ FILE: _site/spec/callbacks.spec.coffee ================================================ {Sequence, Sequence: {do}} = require('../lib/allong.es.js').allong.es double = (v, c) -> c(v * 2) plus1 = (v, c) -> c(v + 1) identity = (v) -> v describe "continuation", -> it "should work for the null do", -> expect( do(Sequence.Callback)(42)(identity) ).toBe 42 it "should work for a double", -> expect( do(Sequence.Callback, double)(42)(identity) ).toBe 84 it "should work for a double double", -> expect( do(Sequence.Callback, double, double)(2)(identity) ).toBe 8 it "should work for a double plus1 double", -> expect( do(Sequence.Callback, double, plus1, double)(2)(identity) ).toBe 10 ================================================ FILE: _site/spec/core.spec.coffee ================================================ # unary, binary, ternary, variadic, compose, sequence??? {defaults, mapWith, getWith, filterWith, compose, sequence, variadic, flip, curry} = require('../lib/allong.es.js').allong.es echo = (a, b, c) -> "#{a} #{b} #{c}" parenthesize = (a) -> "(#{a})" square = (n) -> n * n oddP = (n) -> !!(n % 2) describe "defaults", -> it "should default values", -> expect( defaults(echo, 'c')('a', 'b') ).toEqual 'a b c' expect( defaults(echo, 'b', 'c')('a') ).toEqual 'a b c' expect( defaults(echo, 'a', 'b', 'c')() ).toEqual 'a b c' it "should ignore uneccesary defaults", -> expect( defaults(echo, 'a', 'b', 'c')('A') ).toEqual 'A b c' expect( defaults(echo, 'a', 'b', 'c')('A', 'B') ).toEqual 'A B c' expect( defaults(echo, 'a', 'b', 'c')('A', 'B', 'C') ).toEqual 'A B C' expect( defaults(echo, 'a', 'b', 'c')('A', 'B', 'C', 'D') ).toEqual 'A B C' describe "mapWith", -> it "should map backwards", -> expect( mapWith(square, [1..5]) ).toEqual [1, 4, 9, 16, 25] it "should map backwards, curried", -> expect( mapWith(square)([1..5]) ).toEqual [1, 4, 9, 16, 25] describe "filterWith", -> it "should filter backwards", -> expect( filterWith(oddP, [1..5]) ).toEqual [1, 3, 5] it "should filter backwards, curried", -> expect( filterWith(oddP)([1..5]) ).toEqual [1, 3, 5] describe "compose", -> it "should compose two functions", -> expect( compose(parenthesize, parenthesize)('hello') ).toEqual '((hello))' it "should respect the arity of the first function", -> expect( compose(parenthesize, parenthesize).length ).toEqual 1 expect( compose(echo, parenthesize).length ).toEqual 3 it "should handle a common use case, pluckWith", -> myPluckWith = compose mapWith, getWith expect( myPluckWith('name')([{name: 'foo'}, {name: 'bar'}]) ).toEqual ['foo', 'bar'] expect( myPluckWith('name', [{name: 'foo'}, {name: 'bar'}]) ).toEqual ['foo', 'bar'] describe "sequence", -> it "should sequence two functions", -> expect( sequence(parenthesize, parenthesize)('hello') ).toEqual '((hello))' it "should respect the arity of the first function", -> expect( sequence(parenthesize, parenthesize).length ).toEqual 1 expect( sequence(parenthesize, echo).length ).toEqual 3 it "should handle a common use case, pluckWith", -> myPluckWith = sequence getWith, mapWith expect( myPluckWith('name')([{name: 'foo'}, {name: 'bar'}]) ).toEqual ['foo', 'bar'] expect( myPluckWith('name', [{name: 'foo'}, {name: 'bar'}]) ).toEqual ['foo', 'bar'] describe "flip", -> a = (a) -> [a] b = (a, b) -> [a, b] c = (a, b, c) -> [a, b, c] d = (a, b, c, d) -> [a, b, c, d] v = variadic( (x) -> x ) it "should flip a unary function", -> expect( flip(a)(1) ).toEqual [1] it "should flip a binary function", -> expect( flip(b)(1, 2) ).toEqual [2, 1] it "should flip a ternary function", -> expect( flip(c)(1, 2, 3) ).toEqual [3, 2, 1] it "should flip a quaternary function", -> expect( flip(d)(1, 2, 3, 4) ).toEqual [4, 3, 2, 1] it "should flip a variadic function", -> expect( flip(v)(1, 2, 3, 4, 5) ).toEqual [5, 4, 3, 2, 1] it "should respect arities", -> expect( flip(v).length ).toEqual v.length expect( flip(a).length ).toEqual a.length expect( flip(b).length ).toEqual b.length expect( flip(c).length ).toEqual c.length # expect( flip(d).length ).toEqual d.length # NO; We do not guarantee arity for length > 3 it 'should be self-currying', -> expect( flip(b)(1)(2) ).toEqual [2, 1] expect( flip(c)(1)(2)(3) ).toEqual [3, 2, 1] expect( flip(d)(1)(2)(3)(4) ).toEqual [4, 3, 2, 1] ================================================ FILE: _site/spec/decorating-classes.spec.coffee ================================================ {classDecorator, mixin, fluent} = require('../lib/allong.es.js').allong.es class Todo constructor: (name) -> self = if this instanceof Todo this else new Todo() self.name = name or 'Untitled' self.done = false do: fluent -> this.done = true undo: fluent -> this.done = false describe "features", -> it "should include mixin", -> expect(typeof mixin).toEqual 'function' it "should include classDecorator", -> expect(typeof classDecorator).toEqual 'function' describe "classDecorator", -> AndColourCoded = classDecorator setColourRGB: (r, g, b) -> @colourCode = { r, g, b } this getColourRGB: -> @colourCode ColourTodo = AndColourCoded Todo todo = new ColourTodo('Use More Decorators') .setColourRGB(0, 255, 0) it "should set the name correctly", -> expect( todo.name ).toEqual "Use More Decorators" it "should set the colour code correctly", -> expect( todo.getColourRGB() ).toEqual r: 0 g: 255 b: 0 describe "mixin", -> LocationAware = mixin setLocation: (@location) -> this getLocation: -> @location describe "Using context", -> it "should add location to the existing class's prototype", -> LocationAware.call(Todo.prototype) expect( typeof Todo.prototype.setLocation ).toEqual 'function' expect( typeof Todo.prototype.getLocation ).toEqual 'function' describe "Using a parameter", -> a = { a: 'a' } b = { b: 'b' } c = { c: 'b' } it "should not add the location to the context", -> LocationAware.call(a, b) expect( a.setLocation ).toBeUndefined() expect( a.getLocation ).toBeUndefined() expect( typeof b.setLocation ).toEqual 'function' expect( typeof b.getLocation ).toEqual 'function' it "should add the location when called normally", -> LocationAware(c) expect( typeof c.setLocation ).toEqual 'function' expect( typeof c.getLocation ).toEqual 'function' ================================================ FILE: _site/spec/folding.spec.coffee ================================================ {mapWith, deepMapWith, filter} = require('../lib/allong.es.js').allong.es square = (n) -> n * n describe 'mapWith', -> it 'should square some numbers', -> expect( mapWith(square)([1..5]) ).toEqual [1, 4, 9, 16, 25] describe 'deepMapWith', -> it 'should square some numbers', -> expect( deepMapWith(square)([1, [2..4], 5]) ).toEqual [1, [4, 9, 16], 25] describe "filter", -> it "should find odd numbers", -> expect( filter([1, 2, 3, 4, 5, 6], '% 2 === 1') ).toEqual [1, 3, 5] it "should be self-currying", -> expect( filter([1, 2, 3, 4, 5, 6])('% 2 === 0') ).toEqual [2, 4, 6] ================================================ FILE: _site/spec/iterators.spec.coffee ================================================ {iterators: {slice, drop, take, accumulate, accumulateWithReturn, fold, map, filter, FlatArrayIterator, RecursiveArrayIterator, unfold, unfoldWithReturn}} = require('../lib/allong.es.js').allong.es describe "FlatArrayIterator", -> it "should iterate over a flat array", -> i = FlatArrayIterator([1, 2, 3, 4, 5]) expect( i() ).toEqual(1) expect( i() ).toEqual(2) expect( i() ).toEqual(3) expect( i() ).toEqual(4) expect( i() ).toEqual(5) expect( i() ).toBeUndefined() it "should not iterate down through an array", -> i = FlatArrayIterator([1, [2, 3, [4]], 5]) expect( i() ).toEqual(1) expect( i() ).not.toEqual(2) expect( i() ).toEqual(5) expect( i() ).toBeUndefined() it "should have no values given an empty array", -> i = FlatArrayIterator([]) expect( i() ).toBeUndefined() it "should have a values given an empty tree", -> i = FlatArrayIterator([[], [[]]]) expect( i() ).not.toBeUndefined() describe "RecursiveArrayIterator", -> it "should have no values given an empty array", -> i = RecursiveArrayIterator([]) expect( i() ).toBeUndefined() it "should have no values given an empty tree", -> i = RecursiveArrayIterator([[], [[]]]) expect( i() ).toBeUndefined() it "should iterate over a flat array", -> i = RecursiveArrayIterator([1, 2, 3, 4, 5]) expect( i() ).toEqual(1) expect( i() ).toEqual(2) expect( i() ).toEqual(3) expect( i() ).toEqual(4) expect( i() ).toEqual(5) expect( i() ).toBeUndefined() it "should also iterate down through an array", -> i = RecursiveArrayIterator([1, [2, 3, [4]], 5]) expect( i() ).toEqual(1) expect( i() ).toEqual(2) expect( i() ).toEqual(3) expect( i() ).toEqual(4) expect( i() ).toEqual(5) expect( i() ).toBeUndefined() sum = (x, y) -> x + y describe "fold", -> describe "with a seed", -> it "should fold an iterator with many elements", -> expect( fold(RecursiveArrayIterator([1, [2, 3, [4]], 5]), sum, 0) ).toEqual(15) it "should fold an iterator with one element", -> expect( fold(RecursiveArrayIterator([[[4], []]]), sum, 42) ).toEqual(46) it "should fold an empty iterator", -> expect( fold(RecursiveArrayIterator([[], [[]]]), sum, 42) ).toEqual(42) describe "without a seed", -> it "should fold an array with two or more elements", -> expect( fold(RecursiveArrayIterator([1, [2, 3, [4]], 5]), sum) ).toEqual(15) it "should fold an array with one element", -> expect( fold(RecursiveArrayIterator([[[4], []]]), sum) ).toEqual(4) it "should fold an array with no elements", -> expect( fold(RecursiveArrayIterator([[[], []]]), sum) ).toBeUndefined() describe "accumulate", -> describe "with a seed", -> it "should map an iterator with many elements", -> i = accumulate(RecursiveArrayIterator([1, [2, 3, [4]], 5]), sum, 0) expect( i() ).toEqual(1) expect( i() ).toEqual(3) expect( i() ).toEqual(6) expect( i() ).toEqual(10) expect( i() ).toEqual(15) expect( i() ).toBeUndefined() it "should map an iterator with one element", -> i = accumulate(RecursiveArrayIterator([[[4], []]]), sum, 42) expect( i() ).toEqual(46) expect( i() ).toBeUndefined() it "should map an empty iterator", -> i = accumulate(RecursiveArrayIterator([[[], []]]), sum, 42) expect( i() ).toBeUndefined() describe "without a seed", -> it "should map an iterator with many elements", -> i = accumulate(RecursiveArrayIterator([1, [2, 3, [4]], 5]), sum) expect( i() ).toEqual(1) expect( i() ).toEqual(3) expect( i() ).toEqual(6) expect( i() ).toEqual(10) expect( i() ).toEqual(15) expect( i() ).toBeUndefined() it "should map an iterator with one element", -> i = accumulate(RecursiveArrayIterator([[[4], []]]), sum) expect( i() ).toEqual(4) expect( i() ).toBeUndefined() it "should map an empty iterator", -> i = accumulate(RecursiveArrayIterator([[[], []]]), sum) expect( i() ).toBeUndefined() square = (x) -> x*x describe "map", -> it "should map an iterator with many elements", -> i = map(RecursiveArrayIterator([1, [2, 3, [4]], 5]), square) expect( i() ).toEqual(1) expect( i() ).toEqual(4) expect( i() ).toEqual(9) expect( i() ).toEqual(16) expect( i() ).toEqual(25) expect( i() ).toBeUndefined() it "should map an iterator with one element", -> i = map(RecursiveArrayIterator([[[4], []]]), square) expect( i() ).toEqual(16) expect( i() ).toBeUndefined() it "should map an empty iterator", -> i = map(RecursiveArrayIterator([[[], []]]), square) expect( i() ).toBeUndefined() odd = (x) -> x % 2 is 1 describe "filter", -> it "should filter an iterator with many elements", -> i = filter(RecursiveArrayIterator([1, [2, 3, [4]], 5]), odd) expect( i() ).toEqual(1) expect( i() ).toEqual(3) expect( i() ).toEqual(5) expect( i() ).toBeUndefined() it "should filter an iterator with one element", -> i = filter(RecursiveArrayIterator([[[4], []]]), odd) expect( i() ).toBeUndefined() it "should filter an empty iterator", -> i = filter(RecursiveArrayIterator([[[], []]]), odd) expect( i() ).toBeUndefined() it "should filter an iterator with no matches", -> i = filter(FlatArrayIterator([2, 4, 6, 8, 10]), odd) expect( i() ).toBeUndefined() describe "slice", -> describe "with two parameter", -> it "should return an identity iterator", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 0) expect( i() ).toEqual 1 expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() it "should return a trailing iterator", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 1) expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() it "should return an empty iterator when out of range", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 5) expect( i() ).toBeUndefined() describe "with three parameters", -> it "should return an identity iterator", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 0, 5) expect( i() ).toEqual 1 expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 0, 99) expect( i() ).toEqual 1 expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() it "should return a leading iterator", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 0, 4) expect( i() ).toEqual 1 expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toBeUndefined() it "should return a trailing iterator", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 1, 4) expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 1, 99) expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() it "should return an inner iterator", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 1, 3) expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toBeUndefined() it "should return an empty iterator when given a zero length", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 1, 0) expect( i() ).toBeUndefined() it "should return an empty iterator when out of range", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 5, 1) expect( i() ).toBeUndefined() describe "drop", -> it "should drop the number of items dropped", -> i = drop(FlatArrayIterator([1, 2, 3, 4, 5]), 2) expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() it "should handle overdropping", -> i = drop(FlatArrayIterator([1, 2, 3, 4, 5]), 99) expect( i() ).toBeUndefined() it "should handle underdropping", -> i = drop(FlatArrayIterator([1, 2, 3, 4, 5]), 0) expect( i() ).toEqual 1 expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() it "should default to one", -> i = drop(FlatArrayIterator([1, 2, 3, 4, 5])) expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() describe "accumulateWithReturn", -> it "should pass the state and result in a pair", -> i = accumulateWithReturn(FlatArrayIterator([1, 2, 3, 4, 5]), (state, element) -> [state + element, 'Total is ' + (state + element)] , 0); expect( i() ).toEqual 'Total is 1' expect( i() ).toEqual 'Total is 3' expect( i() ).toEqual 'Total is 6' expect( i() ).toEqual 'Total is 10' expect( i() ).toEqual 'Total is 15' describe "unfold", -> it "should unfold and include the seed", -> i = unfold 0, (n) -> n + 1 expect( i() ).toEqual 0 expect( i() ).toEqual 1 expect( i() ).toEqual 2 it "should not unfold without a seed", -> i = unfold undefined, (n) -> n + 1 expect( i() ).toEqual undefined expect( i() ).toEqual undefined expect( i() ).toEqual undefined expect( i() ).toEqual undefined describe "unfoldWithReturn", -> it "should unfold and throw off a value", -> i = unfoldWithReturn 1, (n) -> [n + 1, n*n] expect( i() ).toEqual 1 expect( i() ).toEqual 4 expect( i() ).toEqual 9 expect( i() ).toEqual 16 it "should halt if it returns undefined", -> i = unfoldWithReturn 1, (n) -> [n + 1, if n is 1 then undefined else n * n] expect( i() ).toEqual undefined expect( i() ).toEqual undefined expect( i() ).toEqual undefined expect( i() ).toEqual undefined it "should halt if the state becomes undefined", -> i = unfoldWithReturn 1, (n) -> [(if n is 3 then undefined else n + 1), (if n is undefined then 100 else n * n)] expect( i() ).toEqual 1 expect( i() ).toEqual 4 expect( i() ).toEqual 9 expect( i() ).toEqual undefined ================================================ FILE: _site/spec/promises.spec.coffee ================================================ Promise = require 'promise' {Sequence, Sequence: {do}} = require('../lib/allong.es.js').allong.es describe "do", -> double = (value) -> new Promise (resolve, reject) -> resolve(value * 2) success = undefined failure = undefined beforeEach -> success = undefined failure = undefined describe "for a doubling promise", -> it "should work asynchronously", (done) -> dodPromise = do(Sequence.Promise, double)(3) dodPromise.then ((value) -> success = value done()), ((reason) -> failure = reason done()) afterEach -> expect( success ).toEqual 6 expect( failure ).toBeUndefined() describe "for a double double", -> it "should work asynchronously", (done) -> dodPromise = do(Sequence.Promise, double, double)(2) dodPromise.then ((value) -> success = value done()), ((reason) -> failure = reason done()) afterEach -> expect( success ).toEqual 8 expect( failure ).toBeUndefined() describe "for a double fail double", -> it "should fail forward", (done) -> failer = (value) -> new Promise (resolve, reject) -> reject 'sorry, old chap' dodPromise = do(Sequence.Promise, double, failer, double)(2) dodPromise.then( ((value) -> success = value done()), ((reason) -> failure = reason done())) afterEach -> expect( success ).toBeUndefined() expect( failure ).toBe 'sorry, old chap' ================================================ FILE: _site/spec/trampoline.spec.coffee ================================================ {trampoline, tailCall} = require('../lib/allong.es.js').allong.es describe "trampolining", -> depth = 32768 it "should execute a clean even/odd to #{depth} levels", -> even = trampoline (n) -> if n is 0 true else tailCall odd, n - 1 odd = trampoline (n) -> if n is 0 false else tailCall even, n - 1 expect( even(0) ).toEqual true expect( even(1) ).toEqual false expect( even(2) ).toEqual true expect( even(3) ).toEqual false expect(-> even(depth) ).not.toThrow() it "should allow tail calling a co-recursive non-trampolined function too", -> even2 = trampoline (n) -> if n is 0 true else tailCall odd2, n - 1 odd2 = (n) -> if n is 0 false else even2 n - 1 expect(-> even2(1000) ).not.toThrow() expect( even2(100) ).toEqual true expect( even2(101) ).toEqual false ================================================ FILE: hello/world.txt ================================================ Hello! ================================================ FILE: lib/allong.es.js ================================================ /*! http://github.com/raganwald/allong.es (c) 2012-2013 Reg Braithwaite MIT Licensed */ (function (root) { // Setup // ----- // Establish the root object, `window` in the browser, or `global` on the server. // *taken from [Underscore.js](http://underscorejs.org/)* var root = this; var __slice = Array.prototype.slice, __map = Array.prototype.map, __hasProp = Array.prototype.hasOwnProperty, __filter = Array.prototype.filter; // See: http://underscorejs.org/docs/underscore.html var nativeIsArray = Array.isArray, toString = Object.prototype.toString, isArray = nativeIsArray || function(obj) { return toString.call(obj) == '[object Array]'; }, isString = function (obj) { return toString.call(obj) == '[object String]'; }, isFunction = function (obj) { return toString.call(obj) == '[object Function]'; }; if (typeof (/./) !== 'function') { isFunction = function(obj) { return typeof obj === 'function'; }; } // here's our export object var allong = { es: {} }; // ## Functionalizing // // The utility functions operate on other functions. They can also operate on string // abbreviations for functions by calling `functionalzie(...)` on their inputs. // SHIM if ('ab'.split(/a*/).length < 2) { if (typeof console !== "undefined" && console !== null) { console.log("Warning: IE6 split is not ECMAScript-compliant. This breaks '->1'"); } } // on the fence about whether to export this? function to_function (str) { var expr, leftSection, params, rightSection, sections, v, vars, _i, _len; params = []; expr = str; sections = expr.split(/\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 { 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 { vars = str.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 (_i = 0, _len = vars.length; _i < _len; _i++) { v = vars[_i]; params.indexOf(v) >= 0 || params.push(v); } } } return new Function(params, 'return (' + expr + ')'); }; function functionalize (fn) { if (typeof fn === 'function') { return fn; } else if (typeof fn === 'string' && /^[_a-zA-Z]\w*$/.test(fn)) { return function() { var args, receiver, _ref; receiver = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; return (_ref = receiver[fn]).call.apply(_ref, [receiver].concat(__slice.call(args))); }; } else if (typeof fn === 'string') { return to_function(fn); } else if (typeof fn.lambda === 'function') { return fn.lambda(); } else if (typeof fn.toFunction === 'function') { return fn.toFunction(); } }; function extend () { var consumer = arguments[0], providers = __slice.call(arguments, 1), key, i, provider, except; for (i = 0; i < providers.length; ++i) { provider = providers[i]; except = provider['except'] || []; except.push('except'); for (key in provider) { if (except.indexOf(key) < 0 && provider.hasOwnProperty(key)) { consumer[key] = provider[key]; }; }; }; return consumer; }; function rotate (array, n) { var copy = array.slice(0), i, pull, push; if (n !== 0) { n || (n = 1); if (n > 0) { pull = 'shift'; push = 'push'; } else { n = -n; pull = 'pop'; push = 'unshift' } for (i = 0; i < n; ++i) { copy[push](copy[pull]()); } } return copy; }; function reverse (array) { return array.reduce(function (acc, element) { acc.unshift(element); return acc; }, []); }; function flatten (array) { var target = []; flattenOntoTarget(array); return target; function flattenOntoTarget (arr) { arr.forEach(function (element) { if (isArray(element)) { flattenOntoTarget(element); } else target.push(element); }); } } extend(allong.es, { flatten: flatten }) // # ARITY function invokeImmediately (fn) { return fn(); }; // ### "Variadic" // // fn = variadic(function (args) { return args }) // // fn() //=> [] // fn(1) //=> [1] // fn(1, 2) //=> [1, 2] // fn(1, 2, 3) //=> [1, 2, 3] // // fn = variadic(function (first, rest) { return [first, rest]}) // // fn() //=> [undefined, []] // fn(1) //=> [1, []] // fn(1, 2) //=> [1, [2]] // fn(1, 2, 3) //=> [1, [2, 3]] // // fn = variadic(function (first, second, rest) { return [first, second, rest]}) // // fn() //=> [undefined, undefined, []] // fn(1) //=> [1, undefined, []] // fn(1, 2) //=> [1, 2, []] // fn(1, 2, 3) //=> [1, 2, [3]] var variadic = function () { var FUNCTIONS = {}; function oldVariadic (fn) { var fnLength = fn.length; if (fnLength < 1) { return fn; } else if (fnLength === 1) { return function () { return fn.call(this, __slice.call(arguments, 0)) } } else { return function () { var numberOfArgs = arguments.length, namedArgs = __slice.call(arguments, 0, fnLength - 1), numberOfMissingNamedArgs = Math.max(fnLength - numberOfArgs - 1, 0), argPadding = new Array(numberOfMissingNamedArgs), variadicArgs = __slice.call(arguments, fn.length - 1); return fn.apply(this, namedArgs.concat(argPadding).concat([variadicArgs])) } } }; return function (arity, fn) { if (fn == null) { fn = functionalize(arity); arity = 0; } else fn = functionalize(fn); var fnLength = fn.length; if (arity === 0) { return oldVariadic(fn); } else if (fnLength <= arity) { var fixedParams = fnLength - 1; var index = '' + arity + '-' + fixedParams; var code; if (FUNCTIONS[index] == null) { var parameters = new Array(arity); for (var i = 0; i < arity; ++i) { parameters[i] = "__" + i; } var pstr = parameters.join(); if (fnLength > 1) { var cstr = parameters.slice(0, fnLength - 1).join(); code = "return function ("+pstr+") { return fn.call("+cstr+", [].slice.call(arguments,"+(fnLength - 1)+")); };"; } else code = "return function ("+pstr+") { return fn.call(this, [].slice.call(arguments, 0)); };"; FUNCTIONS[index] = new Function(['fn'], code); } return FUNCTIONS[index](fn); } else throw 'not supported yet' }; }(); // sets a fixed arity for a function, without currying var unvariadic = (function () { var FUNCTIONS = {}; return function unvariadic (arity, fn) { if (FUNCTIONS[arity] == null) { var parameters = new Array(arity); for (var i = 0; i < arity; ++i) { parameters[i] = "__" + i; } var pstr = parameters.join(); var code = "return function ("+pstr+") { return fn.apply(this, arguments); };"; FUNCTIONS[arity] = new Function(['fn'], code); } if (fn == null) { return function (fn) { return unvariadic(arity, fn); }; } else return FUNCTIONS[arity](functionalize(fn)); }; })(); // a kind of optional semantics: unary(f)(value) === f(value), unary(f)() === unary(f) function unary (fn) { return function unary (a) { if (a == null) { return unary; } else return fn(a); } }; function binary (fn) { return function binary (a, b) { if (a == null) { return binary; } else if (b == null) { return unary(function (b) { return fn(a, b); }); } else return fn(a, b); } }; function ternary (fn) { return function ternary (a, b, c) { if (a == null) { return ternary; } else if (b == null) { return binary(function (b, c) { return fn(a, b, c); }); } else if (c == null) { return unary(function (c) { return fn(a, b, c); }); } else return fn(a, b, c); } }; function quaternary (fn) { return function quaternary (a, b, c, d) { if (a == null) { return quaternary; } else if (b == null) { return ternary(function (b, c, d) { return fn(a, b, c, d); }); } else if (c == null) { return binary(function (c, d) { return fn(a, b, c, d); }); } else if (d == null) { return unary(function (d) { return fn(a, b, c, d); }); } else return fn(a, b, c, d); } }; var byArity = [ invokeImmediately, unary, binary, ternary, quaternary ], byArityLength = byArity.length; function curryWithLeftAndRight (fn, leftArgs, rightArgs) { leftArgs || (leftArgs = []); rightArgs || (rightArgs = []); var fnLength = fn.length, remainingLength = fnLength - leftArgs.length - rightArgs.length; if (remainingLength < byArityLength) { return byArity[remainingLength](handleRemaining); } else return unvariadic(remainingLength, handleRemaining); function handleRemaining () { var params = __slice.call(arguments, 0, arguments.length), numParams = ((params.indexOf(void 0) >= 0) ? params.indexOf(void 0) : params.length), newLeft = leftArgs.concat(params.slice(0, numParams)), args = newLeft.concat(rightArgs), argsLength = args.length, remainingLength = fnLength - argsLength; if (remainingLength <= 0) { return fn.apply(this, args); } else return curryWithLeftAndRight(fn, newLeft, rightArgs); }; }; extend(allong.es, { curryWithLeftAndRight: curryWithLeftAndRight }); // TODO: Deprecate function selfCurrying(fn) { fn = functionalize(fn); var fnLength = fn.length; if (fn.length > 0) { return variadic(fnLength, function (args) { if (args.length === 0) { return curry(fn) } else return fn.apply(this, args); }); } else return fn; }; extend(allong.es, { variadic: variadic, unvariadic: unvariadic, unary: unary, binary: binary, ternary: ternary, quaternary: quaternary }); // ### Composition // compose(a, b, c) // //=> function (x) { // return a(b(c(x))) // } var compose = variadic( function compose (fns) { fns = fns.map(functionalize); var first, firstLength, second, rest, i; if (fns.length === 0) { return function () {}; } else if (fns.length === 1) { return fns[0]; } else if (fns.length === 2) { first = fns[0]; firstLength = first.length; second = fns[1]; if (firstLength === 1) { return function (a) { return first(second(a)); }; } else if (firstLength === 2) { return function (a, b) { return first(second(a), b); }; } else if (firstLength === 3) { return function (a, b, c) { return first(second(a), b, c); }; } else return variadic( function (a, rest) { first.apply(this, [second(a)].concat(rest)) }); } else { first = fns[0]; firstLength = first.length; return function (value) { for (i = fns.length - 1; i >= 0; --i) { value = fns[i](value); } return value; } } }); extend(allong.es, { compose: compose }); // # CALL_FLIPPED var callFlipped = (function () { function nullary (fn) { return variadic( function (args) { return fn.apply(this, reverse(args)); }); }; // a kind of optional semantics: unary(f)(value) === f(value), unary(f)() === unary(f) function unary (fn) { return function unary (a) { if (a == null) { return unary; } else return fn(a); } }; function binary (fn) { return function binary (a, b) { if (a == null) { return binary; } else if (b == null) { return unary(function (b) { return fn(b, a); }); } else return fn(b, a); } }; function ternary (fn) { return function ternary (a, b, c) { if (a == null) { return ternary; } else if (b == null) { return binary(function (c, b) { return fn(c, b, a); }); } else if (c == null) { return unary(function (c) { return fn(c, b, a); }); } else return fn(c, b, a); } }; function quaternary (fn) { return function quaternary (a, b, c, d) { if (a == null) { return quaternary; } else if (b == null) { return ternary(function (d, c, b) { return fn(d, c, b, a); }); } else if (c == null) { return binary(function (d, c) { return fn(d, c, b, a); }); } else if (d == null) { return unary(function (d) { return fn(d, c, b, a); }); } else return fn(d, c, b, a); } }; var byArity = [ nullary, unary, binary, ternary, quaternary ], byArityLength = byArity.length; function callWithLeftFlipped (fn, leftArgs) { leftArgs || ( leftArgs = []); var fnLength = fn.length, remainingLength = fnLength - leftArgs.length; if (remainingLength < byArityLength) { return byArity[remainingLength](handleRemaining); } else return unvariadic(remainingLength, handleRemaining); function handleRemaining () { var params = __slice.call(arguments, 0, arguments.length), numParams = (params.indexOf(void 0) >= 0) ? params.indexOf(void 0) : params.length args = leftArgs.concat(params.slice(0, numParams)); argsLength = args.length, remainingLength = fnLength - argsLength; if (remainingLength <= 0) { return fn.apply(this, reverse(args)); } else return callWithLeftFlipped(fn, args); }; }; return extend( variadic( function callFlipped (fn, args) { fn = functionalize(fn); var fnLength = fn.length, flipped = (fnLength < byArityLength) ? byArity[fnLength](fn) : callWithLeftFlipped(fn); if (args.length === 0) { return flipped; } else return flipped.apply(this, args); }), { unary: unary, binary: binary, ternary: ternary, quaternary: quaternary, callWithLeftFlipped: callWithLeftFlipped }); })(); // synonymish var flip = unary(callFlipped); extend(allong.es, { callFlipped: callFlipped, flip: flip }); // # APPLY // the basics: apply and call // call that retuns a curried function var apply = binary( function (fn, args) { return curryWithLeftAndRight(fn, args, []); }); var call = variadic( function (fn, args) { return curryWithLeftAndRight(fn, args, []); }); var curry = unary(call); function urApply (fn, args) { return fn.apply(this, args); }; var applyNow = call( urApply ); var callNow = extend( variadic(applyNow), { unary: unary, binary: binary, ternary: ternary, quaternary: quaternary }); // flipped forms var applyNowFlipped = flip(urApply); // apply and call left function urApplyLeft (fn, leftArgs) { var remainingLength = fn.length - leftArgs.length; if (fn.length > 0 && remainingLength > 0) { return variadic(remainingLength, function (args) { return fn.apply(this, leftArgs.concat(args)); }); } else return fn.apply(this, leftArgs); }; var applyLeftNow = call(urApplyLeft); var callLeftNow = variadic(urApplyLeft); // flipped var applyLeftNowWith = flip(urApplyLeft); var applyRightNowWith = flip(urApplyRight); // rightmost function urApplyRight (fn, rightArgs) { var remainingLength = fn.length - rightArgs.length; if (fn.length > 0 && remainingLength > 0) { return variadic(remainingLength, function (args) { return fn.apply(this, args.concat(rightArgs)); }); } else return fn.apply(this, rightArgs); } var applyRightNow = call(urApplyRight); var callRightNow = variadic(urApplyRight); var callRight = variadic( function (fn, args) { return curryWithLeftAndRight(fn, [], args); }); var callFirst = binary(call); var callFirstWith = flip(callFirst); var callLast = binary(callRight); var callLastWith = flip(callLast); // ### Partial applications that bind // A partially applied binding function // // roughly equivalent to applyRight // // var fn = function (...) { ... } // // bound(fn)(x) // //=> fn.bind(x) // // bound(fn, foo)(x) // //=> fn.bind(x, foo) // // bound(fn, foo, bar)(x) // //=> fn.bind(x, foo, bar) var bound = variadic( function (messageName, args) { if (args === []) { return function (instance) { return instance[messageName].bind(instance) } } else { return function (instance) { return Function.prototype.bind.apply( instance[messageName], [instance].concat(args) ) } } }); var defaults = variadic( function (fn, values) { var fln = fn.length, vln = values.length; return variadic( function (args) { var aln = args.length, mln = Math.max(fln - aln, 0); args = args.concat(values.slice(vln-mln)); return fn.apply(this, args); }) }); // collects arguments function args (arity) { if (arity === 1) { return function (a) { return [a]; }; } else if (arity === 2) { return function (a, b) { return [a, b]; }; } else if (arity === 3) { return function (a, b, c) { return [a, b, c]; }; } else return variadic( function (args) { return args; }); }; extend(allong.es, { applyNow: applyNow, apply: apply, callNow: callNow, call: call, callLeft: call, callRight: callRight, applyNowFlipped: applyNowFlipped, applyLeftNow: applyLeftNow, callLeftNow: callLeftNow, applyLeftNowWith: applyLeftNowWith, applyRightNow: applyRightNow, callRightNow: callRightNow, applyRightNowWith: applyRightNowWith, callFirst: callFirst, callLast: callLast, callFirstWith: callFirstWith, callLastWith: callLastWith, bound: bound, defaults: defaults, args: args, curry: curry }); // # FOLDING var filter = binary( function filter (list, fn) { fn = functionalize(fn); return __filter.call(list, fn); }); var filterWith = flip(filter); var map = binary( function map (list, fn) { fn = functionalize(fn); var fnLength = fn.length; if (fnLength !== 1) { fn = unary(fn); } return __map.call(list, fn); }); var mapWith = flip(map); var mapArgumentsWith = binary( function (withFn, fn) { return variadic(fn.length, function (args) { return applyNow(fn, map(args, withFn)); }); }); // turns any function into a recursive mapper // // deepMap(function (x) { return x * x })([1, [2, 3], 4]) // //=> [1, [4, 9], 16] // function deepMap (fn) { // return function innerDeepMap (tree) { // return __map.call(tree, function (element) { // if (Array.isArray(element)) { // return innerSoak(element); // } // else return fn(element); // }); // }; // }; var deepMap = binary( function (tree, fn) { fn = functionalize(fn); return __map.call(tree, function (element) { if (Array.isArray(element)) { return deepMap(element, fn); } else return fn(element); }); }); var deepMapWith = flip(deepMap); extend(allong.es, { map: map, mapWith: mapWith, mapArgumentsWith: mapArgumentsWith, filter: filter, filterWith: filterWith, deepMap: deepMap, deepMapWith: deepMapWith }); // # DECORATORS function mapIfMany (fn) { fn = functionalize(fn); return variadic( function (argList) { if (argList.length === 1 && !isArray(argList[0])) { return fn.call(this, argList[0]); } else return deepMap(argList, fn); }); } var maybe = mapIfMany( function maybe (fn) { fn = functionalize(fn); return function () { var i; if (arguments.length === 0) { return } else { for (i = 0; i < arguments.length; ++i) { if (arguments[i] == null) return arguments[i]; } return fn.apply(this, arguments) } } }); function tap (value, fn) { fn = functionalize(fn); if (fn === void 0) { return curried } else return curried(fn); function curried (fn) { if (typeof(fn) === 'function') { fn(value) } return value } } function fluent (fn) { fn = functionalize(fn); return function () { fn.apply(this, arguments); return this } } // decorates a function to return its first argument var returnFirst = function (fn) { fn = functionalize(fn); return function () { fn.apply(this, arguments); return arguments[0]; } } function tee (decoration) { decoration = functionalize(decoration); return function (fn) { fn = functionalize(fn); return compose(returnFirst(decoration), fn); }; }; function once (fn) { fn = functionalize(fn); var done = false, testAndSet; if (!!fn.name) { testAndSet = function () { this["__once__"] || (this["__once__"] = {}) if (this["__once__"][fn.name]) return true; this["__once__"][fn.name] = true; return false } } else { testAndSet = function (fn) { if (done) return true; done = true; return false } } return function () { return testAndSet.call(this) ? void 0 : fn.apply(this, arguments) } } function memoized (fn, keymaker) { fn = functionalize(fn); var lookupTable = {}, key, value; keymaker || (keymaker = function (args) { return JSON.stringify(args) }); return function () { var key = keymaker.call(this, arguments); return lookupTable[key] || ( lookupTable[key] = fn.apply(this, arguments) ) } }; extend(allong.es, { maybe: maybe, tap: tap, fluent: fluent, returnFirst: returnFirst, tee: tee, once: once, memoized: memoized }); // # MIXIN function mixin (decoration) { return function decorator () { if (arguments[0] !== void 0) { return decorator.call(arguments[0]); } else { extend(this, decoration); return this; } }; }; function classDecorator (decoration) { return function (clazz) { function Decorated () { var self = this instanceof Decorated ? this : new Decorated(); return clazz.apply(self, arguments); }; Decorated.prototype = extend(new clazz(), decoration); return Decorated; }; }; extend(allong.es, { mixin: mixin, classDecorator: classDecorator }); // # MORE var unbind = function unbind (fn) { fn = functionalize(fn); return fn.unbound ? unbind(fn.unbound()) : fn }; function bind (fn, context, force) { fn = functionalize(fn); var unbound, bound; if (force) { fn = unbind(fn) } bound = function () { return fn.apply(context, arguments) }; bound.unbound = function () { return fn; }; return bound; } function invoke (fn) { fn = functionalize(fn); var args = __slice.call(arguments, 1); return function (instance) { return fn.apply(instance, args) } }; function get (object, getName) { if (getName == null) { return function (getName) { return object[getName]; }; } else return object[getName]; }; function getWith (getName, object) { if (object == null) { return function (object) { return object[getName]; }; } else return object[getName]; }; var pluckWith = compose(mapWith, getWith), pluck = flip(pluckWith); // Send a message/invoke a method on the receiver. // TODO: Think about what it has in common with callLeft var send = variadic( function (methodName, args) { return variadic( function (receiver, remainingArgs) { var fn = receiver[methodName]; return fn.apply(receiver, args.concat(remainingArgs)) }) }); extend(allong.es, { bind: bind, unbind: unbind, invoke: invoke, get: get, getWith: getWith, send: send, pluckWith: pluckWith, pluck: pluck }); // # TRAMPOLINE function Thunk (closure) { if (!(this instanceof Thunk)) return new Thunk(closure); this.closure = closure; }; Thunk.prototype.force = function () { return this.closure(); }; function trampoline (fn) { var trampolined = variadic( function (args) { var result = fn.apply(this, args); while (result instanceof Thunk) { result = result.force(); } return result; }); trampolined.__trampolined_fn = fn; return trampolined; }; var tailCall = variadic( function (fn, args) { var context = this; if (fn.__trampolined_fn instanceof Function) { return new Thunk( function () { return fn.__trampolined_fn.apply(context, args); }); } else return new Thunk( function () { return fn.apply(context, args); }); }); extend(allong.es, { trampoline: trampoline, tailCall: tailCall, Thunk: Thunk }); // # ITERATORS (function (allong) { function fold (iter, binaryFn, seed) { var state, element; binaryFn = functionalize(binaryFn); if (seed !== void 0) { state = seed; } else { state = iter(); } element = iter(); while (element != null) { state = binaryFn.call(element, state, element); element = iter(); } return state; }; var HASNTBEENRUN = {}; function unfold (seed, unaryFn) { var state = HASNTBEENRUN; unaryFn = functionalize(unaryFn); return function () { if (state === HASNTBEENRUN) { return (state = seed); } else if (state != null) { return (state = unaryFn.call(state, state)); } else return state; }; }; // note that the unfoldWithReturn behaves differently than // unfold with respect to the first value returned function unfoldWithReturn (seed, unaryFn) { var state = seed, pair, value; unaryFn = functionalize(unaryFn); return function () { if (state != null) { pair = unaryFn.call(state, state); value = pair[1]; state = value != null ? pair[0] : void 0 return value; } else return void 0; }; }; function accumulate (iter, binaryFn, initial) { var state = initial; binaryFn = functionalize(binaryFn); return function () { element = iter(); if (element == null) { return element; } else { if (state === void 0) { return (state = element); } else return (state = binaryFn.call(element, state, element)); } } }; function accumulateWithReturn (iter, binaryFn, initial) { var state = initial, stateAndReturnValue; binaryFn = functionalize(binaryFn); return function () { element = iter(); if (element == null) { return element; } else { if (state === void 0) { return (state = element); } else { stateAndReturnValue = binaryFn.call(element, state, element); state = stateAndReturnValue[0]; return stateAndReturnValue[1]; } } } }; function map (iter, unaryFn) { unaryFn = functionalize(unaryFn); return function() { var element; element = iter(); if (element != null) { return unaryFn.call(element, element); } else { return void 0; } }; }; function select (iter, unaryPredicateFn) { unaryPredicateFn = functionalize(unaryPredicateFn); return function() { var element; element = iter(); while (element != null) { if (unaryPredicateFn.call(element, element)) { return element; } element = iter(); } return void 0; }; }; function reject (iter, unaryPredicateFn) { unaryPredicateFn = functionalize(unaryPredicateFn); return select(iter, function (something) { return !unaryPredicateFn(something); }); }; function find (iter, unaryPredicateFn) { unaryPredicateFn = functionalize(unaryPredicateFn); return select(iter, unaryPredicateFn)(); } function slice (iter, numberToDrop, numberToTake) { var count = 0; while (numberToDrop-- > 0) { iter(); } if (numberToTake != null) { return function() { if (++count <= numberToTake) { return iter(); } else { return void 0; } }; } else return iter; }; var drop = defaults(binary(slice), 1); function take (iter, numberToTake) { return slice(iter, 0, numberToTake == null ? 1 : numberToTake); } function FlatArrayIterator (array) { var index = 0; return function() { return array[index++]; }; }; function RecursiveArrayIterator (array) { var index, myself, state; index = 0; state = []; myself = function() { var element, tempState; element = array[index++]; if (element instanceof Array) { state.push({ array: array, index: index }); array = element; index = 0; return myself(); } else if (element === void 0) { if (state.length > 0) { tempState = state.pop(), array = tempState.array, index = tempState.index; return myself(); } else { return void 0; } } else { return element; } }; return myself; }; function K (value) { return function () { return value; }; }; function upRange (from, to, by) { return function () { var was; if (from > to) { return void 0; } else { was = from; from = from + by; return was; } } }; function downRange (from, to, by) { return function () { var was; if (from < to) { return void 0; } else { was = from; from = from - by; return was; } } }; function range (from, to, by) { if (from == null) { return upRange(1, Infinity, 1); } else if (to == null) { return upRange(from, Infinity, 1); } else if (by == null) { if (from <= to) { return upRange(from, to, 1); } else return downRange(from, to, 1) } else if (by > 0) { return upRange(from, to, by); } else if (by < 0) { return downRange(from, to, Math.abs(by)) } else return k(from); }; var numbers = unary(range); extend(allong.es, { iterators: { accumulate: accumulate, accumulateWithReturn: accumulateWithReturn, fold: fold, unfold: unfold, unfoldWithReturn: unfoldWithReturn, map: map, select: select, reject: reject, filter: select, find: find, slice: slice, drop: drop, take: take, FlatArrayIterator: FlatArrayIterator, RecursiveArrayIterator: RecursiveArrayIterator, constant: K, K: K, numbers: numbers, range: range }}); })(allong); // Monadic Sequencing // ------------------ // sequence(a, b, c) // //=> function (x) { // return c(b(a(x))) // } // var sequence = callFlipped(compose); var Promise = require('promise'); var BaseMonad = function (methods) { var name, body; if (methods) { for (name in methods) { if (methods.hasOwnProperty(name)) { this[name] = methods[name]; } } } }; BaseMonad.prototype = { of: function (value) { return value; }, map: function (fn) { return fn; }, chain: function (mValue, fn) { return this.map(fn)(mValue); } }; var Identity = new BaseMonad(); var Maybe = new BaseMonad({ map: maybe }); var Writer = new BaseMonad({ of: function(value) { return [value, '']; }, map: function(fn) { return function(_arg) { var newlyWritten, result, value, writtenSoFar, _ref; value = _arg[0], writtenSoFar = _arg[1]; _ref = fn(value), result = _ref[0], newlyWritten = _ref[1]; return [result, writtenSoFar + newlyWritten]; }; } }); var ArrayWriter = new BaseMonad({ of: function (argument) { return [argument, []]; }, chain: function (valueAndLogList, fn) { var value = valueAndLogList[0], logList = valueAndLogList[1], resultAndLogList = fn(value), result = resultAndLogList[0], resultLogList = flatten(logList.concat(resultAndLogList[1])); return [result, resultLogList]; } }); var List = new BaseMonad({ of: function(value) { return [value]; }, join: function(mValue) { return mValue.reduce(this.concat, this.zero()); }, map: function(fn) { return function(mValue) { return mValue.map(fn); }; }, zero: function() { return []; }, concat: function(ma, mb) { return ma.concat(mb); }, chain: function(mValue, fn) { return this.join(this.map(fn)(mValue)); } }); var Then = new BaseMonad({ of: function(value) { return new Promise(function(resolve, reject) { return resolve(value); }); }, map: function(fnReturningAPromise) { return function(promiseIn) { return new Promise(function(resolvePromiseOut, rejectPromiseOut) { return promiseIn .then(fnReturningAPromise, rejectPromiseOut) .then(resolvePromiseOut, rejectPromiseOut) }); }; } }); var Callback = { chain: function (mValue, fn) { if (mValue.length === 1) { return function (value, callback) { return mValue(value, variadic( function (results) { return fn.apply(null, results.concat([callback])); })); }; } else { // needs variadicr return variadic( function (args) { var callback = args[args.length - 1], values = __slice.call(args, 0, args.length - 1); return mValue.apply(this, values.concat([ variadic( function (results) { return fn.apply(null, results.concat([callback])); })])); }); } } }; var pipeline = flip(compose); var sequence = variadic( function (sequenceArgs) { var fns, supervisor, arity, _of, _map, _chain; if (sequenceArgs.length === 0) { return function (value) { return value; } // I combinator } else { sequenceArgs = flatten(sequenceArgs); if (isFunction(sequenceArgs[0])) { return pipeline.apply(this, sequenceArgs); } else { if (isString(sequenceArgs[0])) { supervisor = sequence[sequenceArgs[0]]; } else supervisor = sequenceArgs[0]; _map = (supervisor.map && supervisor.map.bind(supervisor)); _chain = (supervisor.chain && supervisor.chain.bind(supervisor)); _of = (supervisor.of && supervisor.of.bind(supervisor)); // default for _chain if (_chain == null) { _chain = (_map == null) ? function (mValue, fn) { return fn(mValue); } // "T" : function (mValue, fn) { return _map(fn)(mValue); } } fns = 2 <= sequenceArgs.length ? __slice.call(sequenceArgs, 1) : []; arity = fns.length > 0 ? fns[0].length : 0; if (isFunction(_of)) { // option 1: There is an _of: uses this function to provide a seed return variadic(arity, function (args) { return fns.reduce(_chain, _of.apply(this, args)); }); } else { // option 2: uses the first function as the seed chained = fns.slice(1).reduce(_chain, fns[0]); return unvariadic(arity, chained); } } } }); extend(sequence, { Identity: Identity, Maybe: Maybe, List: List, Writer: Writer, Then: Then, Callback: Callback }); extend(allong.es, { pipeline: pipeline, sequence: sequence }); // andand, oror // ------------ var andand = mapIfMany( function andand (fn) { return variadic(fn.length, function (args) { if (args.length > 0) { var argTruthiness = args.reduce(function (acc, value) { return acc && value; }); return argTruthiness && fn.apply(this, args); } }) }) var oror = mapIfMany( function oror (fn) { return variadic(fn.length, function (args) { if (args.length > 0) { var argTruthiness = args.reduce(function (acc, value) { return acc || value; }); return argTruthiness || fn.apply(this, args); } }) }); extend(allong.es, { andand: andand, oror: oror }); // Sequence Transformers // --------------------- var fn2Then = mapIfMany( function fn2Then (fn) { return function (value) { return new Promise(function(resolve, reject) { return resolve(fn(value)); }); } }); var callback2Then = mapIfMany( function callback2Then (fn) { if (fn.length === 1) { return function () { return new Promise(function(resolve, reject) { return fn(function (value) { return resolve(value); }) }); }; } else return variadic(fn.length - 1, function(values) { return new Promise(function(resolve, reject) { return fn.apply(this, values.concat([function (value) { return resolve(value); }])); }); }); }); var fn2Callback = mapIfMany( function fn2Callback (fn) { return function (value, callback) { return callback(fn(value)); } }); var then2Callback = mapIfMany( function fn2Callback (fn) { return function (value, callback) { var thennable = fn(value); thennable.then(callback, function (error) { throw error; }); } }); extend(sequence, { fn2Then: fn2Then, fn2Callback: fn2Callback, callback2Then: callback2Then, then2Callback: then2Callback }); // Exports and sundries // -------------------- if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = allong; } exports.allong = allong; } else { root.allong = allong; } }).call(this); ================================================ FILE: package.json ================================================ { "author": "Reg Braithwaite (http://braythwayt.com)", "name": "allong.es", "description": "Combinators and Function Decorators", "version": "0.14.0", "homepage": "http://allong.es", "repository": { "type": "git", "url": "git://github.com/raganwald/allong.es.git" }, "main": "lib/allong.es.js", "scripts": { "test": "jasmine-node --coffee --verbose spec" }, "engines": { "node": "" }, "dependencies": { "promise": "" }, "devDependencies": { "jasmine-node": "", "coffee-script": "", "grunt": "~0.4.1", "grunt-contrib-uglify": "~0.2.0" } } ================================================ FILE: spec/andand-oror.spec.coffee ================================================ {andand, oror} = require('../lib/allong.es').allong.es T = () -> true F = () -> false N = () -> null U = () -> undefimed describe "andand", -> it " should pass null through", -> expect( andand(T)(null) ).toEqual(null && true) it " should pass undefined through", -> expect( andand(T)(undefined) ).toEqual(undefined && true) it " should pass false through", -> expect( andand(T)(false) ).toEqual(false && true) it 'should evaluate on truthy', -> expect( andand(T)('truthy') ).toEqual('truthy' && true) it 'should evaluate on []', -> expect( andand(T)([]) ).toEqual([] && true) it 'should evaluate on \'\'', -> expect( andand(T)('') ).toEqual('' && true) it 'should evaluate on 0', -> expect( andand(T)(0) ).toEqual(0 && true) describe "oror", -> it " should pass null through", -> expect( oror(T)(null) ).toEqual(null || true) it " should pass undefined through", -> expect( oror(T)(undefined) ).toEqual(undefined || true) it " should pass false through", -> expect( oror(T)(false) ).toEqual(false || true) it 'should evaluate on truthy', -> expect( oror(T)('truthy') ).toEqual('truthy' || true) it 'should evaluate on []', -> expect( oror(T)([]) ).toEqual([] || true) it 'should evaluate on \'\'', -> expect( oror(T)('') ).toEqual('' || true) it 'should evaluate on 0', -> expect( oror(T)(0) ).toEqual(0 || true) ================================================ FILE: spec/apply.spec.coffee ================================================ { callRight, applyNow, callNow, applyNowFlipped, call, applyLeftNow, callLeftNow, args, applyLeftNowWith, applyRightNow, callRightNow, applyRightNowWith, callFirst, callFirstWith, callLast, callLastWith, apply } = require('../lib/allong.es.js').allong.es echo = (a, b, c) -> "#{a} #{b} #{c}" five = (a, b, c, d, e) -> [a, b, c, d, e] three = (a, b, c) -> [a, b, c] twelve = (a, b, c, d, e, f, g, h, i, j, k, l) -> vari = (args...) -> args one = (x) -> x describe "apply", -> it "should apply an array of arguments to a function", -> expect( apply(three, [1, 2, 3]) ).toEqual three(1, 2, 3) it "should be curried", -> expect( apply(three)([1, 2, 3]) ).toEqual three(1, 2, 3) it "should be self-currying, it should apply what it gets", -> expect( apply(three, [1, 2])(3) ).toEqual three(1, 2, 3) describe "applyNow", -> it "should apply an array of arguments to a function", -> expect( applyNow(three, [1, 2, 3]) ).toEqual three(1, 2, 3) it "should not be self-currying, it should apply what it gets", -> expect( applyNow(three, [1, 2]) ).toEqual three(1, 2) expect( applyNow(three, [1]) ).toEqual three(1) it "should be curried", -> expect( applyNow(three)([1, 2, 3]) ).toEqual three(1, 2, 3) describe "when flipped", -> it "should apply an array of arguments to a function", -> expect( applyNowFlipped([1, 2, 3], three) ).toEqual three(1, 2, 3) it "should be curried", -> expect( applyNowFlipped([1, 2, 3])(three) ).toEqual three(1, 2, 3) describe "callNow", -> it "should apply arguments to a function", -> expect( callNow(three, 1, 2, 3) ).toEqual three(1, 2, 3) it "should not be self-currying, it should apply what it gets", -> expect( callNow(three, 1, 2) ).toEqual three(1, 2) expect( callNow(three, 1) ).toEqual three(1) it "should not be curried", -> expect( callNow(three) ).toEqual three() # variadic functions do not have a 'this' predefined at this point. describe "applyLeftNow", -> it 'should apply all the arguments if possible', -> expect( applyLeftNow(three, [1, 2, 3]) ).toEqual three(1, 2, 3) it 'should not be full application', -> expect( applyLeftNow(three, [1, 2]) ).not.toEqual three(1, 2) expect( applyLeftNow(three, [1, 2])(3) ).toEqual three(1, 2, 3) it 'should not be fully curried', -> expect( applyLeftNow(three, [1])(2) ).toEqual three(1, 2) describe "when flipped", -> it 'should apply all the arguments if possible', -> expect( applyLeftNowWith([1, 2, 3], three) ).toEqual three(1, 2, 3) it 'should not be full application', -> expect( applyLeftNowWith([1, 2], three) ).not.toEqual three(1, 2) expect( applyLeftNowWith([1, 2], three)(3) ).toEqual three(1, 2, 3) it 'should not be fully curried', -> expect( applyLeftNowWith([1], three)(2) ).toEqual three(1, 2) describe "callLeftNow", -> it 'should apply all the arguments if possible', -> expect( callLeftNow(three, 1, 2, 3) ).toEqual three(1, 2, 3) it 'should not be full application', -> expect( callLeftNow(three, 1, 2) ).not.toEqual three(1, 2) expect( callLeftNow(three, 1, 2)(3) ).toEqual three(1, 2, 3) it 'should not be fully curried', -> expect( callLeftNow(three, 1)(2) ).toEqual three(1, 2) describe "applyRightNow", -> it 'should apply all the arguments if possible', -> expect( applyRightNow(three, [1, 2, 3]) ).toEqual three(1, 2, 3) it 'should not be full application', -> expect( applyRightNow(three, [1, 2]) ).not.toEqual three(1, 2) expect( applyRightNow(three, [1, 2])(3) ).toEqual three(3, 1, 2) it 'should not be fully curried', -> expect( applyRightNow(three, [1])(2) ).toEqual three(2, 1) describe "when flipped", -> it 'should apply all the arguments if possible', -> expect( applyRightNowWith([1, 2, 3], three) ).toEqual three(1, 2, 3) it 'should not be full application', -> expect( applyRightNowWith([1, 2], three) ).not.toEqual three(1, 2) expect( applyRightNowWith([1, 2], three)(3) ).toEqual three(3, 1, 2) it 'should not be fully curried', -> expect( applyRightNowWith([1], three)(2) ).toEqual three(2, 1) describe "callRightNow", -> it 'should apply all the arguments if possible', -> expect( callRightNow(three, 1, 2, 3) ).toEqual three(1, 2, 3) it 'should not be full application', -> expect( callRightNow(three, 1, 2) ).not.toEqual three(1, 2) expect( callRightNow(three, 1, 2)(3) ).toEqual three(3, 1, 2) it 'should not be fully curried', -> expect( callRightNow(three, 1)(2) ).toEqual three(2, 1) ################################ describe "call", -> it "should call an array of arguments to a function", -> expect( call(echo, 1, 2, 3) ).toEqual "1 2 3" it "should have a curried nature", -> expect( call(five)(1, 2, 3, 4, 5) ).toEqual [1..5] expect( call(five)(1, 2, 3)(4, 5) ).toEqual [1..5] expect( call(five, 1, 2, 3)(4, 5) ).toEqual [1..5] expect( call(five, 1, 2, 3)(4)(5) ).toEqual [1..5] it "should get the arity right for small amounts", -> expect( call(five, 1, 2).length ).toEqual 3 describe "callRight", -> it "should call an array of arguments to a function", -> expect( callRight(echo, 1, 2, 3) ).toEqual "1 2 3" it "should have a curried nature", -> expect( callRight(five)(1, 2, 3, 4, 5) ).toEqual [1..5] expect( callRight(five)(1, 2, 3)(4, 5) ).toEqual [1..5] it "should apply given arguments to the right", -> expect( callRight(five, 1)(2, 3, 4, 5) ).toEqual [2, 3, 4, 5, 1] expect( callRight(five, 1, 2)(3, 4, 5) ).toEqual [3, 4, 5, 1, 2] expect( callRight(five, 1, 2, 3)(4, 5) ).toEqual [4, 5, 1, 2, 3] expect( callRight(five, 1, 2, 3, 4)(5) ).toEqual [5, 1, 2, 3, 4] expect( callRight(five, 1, 2, 3)(4, 5) ).toEqual [4, 5, 1, 2, 3] expect( callRight(five, 1, 2, 3)(4)(5) ).toEqual [4, 5, 1, 2, 3] it "should get the arity right for small amounts", -> expect( callRight(five, 1, 2).length ).toEqual 3 describe 'callFirst', -> it 'should call with the first argument', -> expect( callFirst(three, 1)(2, 3) ).toEqual [1..3] it 'should get the arity right', -> expect( callFirst.length ).toEqual 2 expect( callFirst(three, 1).length ).toEqual 2 it 'should be curried', -> expect( callFirst(three)(1)(2, 3) ).toEqual [1..3] describe 'callFirstWith', -> it 'should call with the first argument', -> expect( callFirstWith(1, three)(2, 3) ).toEqual [1..3] it 'should get the arity right', -> expect( callFirstWith.length ).toEqual 2 expect( callFirstWith(1, three).length ).toEqual 2 it 'should be curried', -> expect( callFirstWith(1)(three)(2, 3) ).toEqual [1..3] describe "args", -> it "should collect arguments into an array", -> expect( args(3)(1, 2, 3) ).toEqual [1, 2, 3] ================================================ FILE: spec/arity.spec.coffee ================================================ {unvariadic, variadic, curry} = require('../lib/allong.es.js').allong.es numberOfArgs = -> arguments.length threeArguments = (a, b, c) -> arguments.length describe "unvariadic", -> it "should clip arguments", -> expect( unvariadic(3, numberOfArgs)(1, 2, 3, 4, 5) ).toEqual threeArguments(1, 2, 3, 4, 5) it "shouldn't pad arguments", -> expect( unvariadic(3, numberOfArgs)(1) ).toEqual threeArguments(1) describe "variadic", -> describe "with an arity", -> one = (args) -> [args] two = (arg, args) -> [arg, args] onev = (args...) -> [args] twov = (arg, args...) -> [arg, args] it 'should 1', -> expect( variadic(1, one)(1, 2, 3) ).toEqual onev(1, 2, 3) it 'should 2', -> expect( variadic(1, one).length ).toEqual 1 it 'should 3', -> expect( variadic(2, one).length ).toEqual 2 describe "curry", -> three = (a, b, c) -> [a, b, c] it "should be a function that returns a function", -> expect( curry instanceof Function).toEqual true expect( curry(three) instanceof Function).toEqual true it "should allow a full invocation", -> expect( curry(three)(1, 2, 3) ).toEqual [1, 2, 3] it "should allow partial invocation", -> expect( curry(three)(1)(2, 3) ).toEqual [1, 2, 3] expect( curry(three)()(1, 2, 3) ).toEqual [1, 2, 3] expect( curry(three)(1)(2)(3) ).toEqual [1, 2, 3] expect( curry(three)(1, 2)(3) ).toEqual [1, 2, 3] # describe "self-currying", -> # # it "shouldn't affect normal useage", -> # expect(selfCurrying(three)(4, 5, 6)).toEqual three(4, 5, 6) # expect(selfCurrying(three)(4, 5)).toEqual three(4, 5) # expect(selfCurrying(three)(4)).toEqual three(4) # # it "should curry with no arguments", -> # expect( selfCurrying(three)()(1)(2, 3) ).toEqual [1, 2, 3] # expect( selfCurrying(three)()()(1, 2, 3) ).toEqual [1, 2, 3] # expect( selfCurrying(three)()(1)(2)(3) ).toEqual [1, 2, 3] # expect( selfCurrying(three)()(1, 2)(3) ).toEqual [1, 2, 3] ================================================ FILE: spec/core.spec.coffee ================================================ # unary, binary, ternary, variadic, compose, sequence??? {defaults, mapWith, getWith, filterWith, compose, sequence, variadic, flip, curry} = require('../lib/allong.es.js').allong.es echo = (a, b, c) -> "#{a} #{b} #{c}" parenthesize = (a) -> "(#{a})" square = (n) -> n * n oddP = (n) -> !!(n % 2) describe "defaults", -> it "should default values", -> expect( defaults(echo, 'c')('a', 'b') ).toEqual 'a b c' expect( defaults(echo, 'b', 'c')('a') ).toEqual 'a b c' expect( defaults(echo, 'a', 'b', 'c')() ).toEqual 'a b c' it "should ignore uneccesary defaults", -> expect( defaults(echo, 'a', 'b', 'c')('A') ).toEqual 'A b c' expect( defaults(echo, 'a', 'b', 'c')('A', 'B') ).toEqual 'A B c' expect( defaults(echo, 'a', 'b', 'c')('A', 'B', 'C') ).toEqual 'A B C' expect( defaults(echo, 'a', 'b', 'c')('A', 'B', 'C', 'D') ).toEqual 'A B C' describe "mapWith", -> it "should map backwards", -> expect( mapWith(square, [1..5]) ).toEqual [1, 4, 9, 16, 25] it "should map backwards, curried", -> expect( mapWith(square)([1..5]) ).toEqual [1, 4, 9, 16, 25] describe "filterWith", -> it "should filter backwards", -> expect( filterWith(oddP, [1..5]) ).toEqual [1, 3, 5] it "should filter backwards, curried", -> expect( filterWith(oddP)([1..5]) ).toEqual [1, 3, 5] describe "compose", -> it "should compose two functions", -> expect( compose(parenthesize, parenthesize)('hello') ).toEqual '((hello))' it "should respect the arity of the first function", -> expect( compose(parenthesize, parenthesize).length ).toEqual 1 expect( compose(echo, parenthesize).length ).toEqual 3 it "should handle a common use case, pluckWith", -> myPluckWith = compose mapWith, getWith expect( myPluckWith('name')([{name: 'foo'}, {name: 'bar'}]) ).toEqual ['foo', 'bar'] expect( myPluckWith('name', [{name: 'foo'}, {name: 'bar'}]) ).toEqual ['foo', 'bar'] describe "sequence", -> it "should sequence two functions", -> expect( sequence(parenthesize, parenthesize)('hello') ).toEqual '((hello))' it "should respect the arity of the first function", -> expect( sequence(parenthesize, parenthesize).length ).toEqual 1 expect( sequence(parenthesize, echo).length ).toEqual 3 it "should handle a common use case, pluckWith", -> myPluckWith = sequence getWith, mapWith expect( myPluckWith('name')([{name: 'foo'}, {name: 'bar'}]) ).toEqual ['foo', 'bar'] expect( myPluckWith('name', [{name: 'foo'}, {name: 'bar'}]) ).toEqual ['foo', 'bar'] describe "flip", -> a = (a) -> [a] b = (a, b) -> [a, b] c = (a, b, c) -> [a, b, c] d = (a, b, c, d) -> [a, b, c, d] v = variadic( (x) -> x ) it "should flip a unary function", -> expect( flip(a)(1) ).toEqual [1] it "should flip a binary function", -> expect( flip(b)(1, 2) ).toEqual [2, 1] it "should flip a ternary function", -> expect( flip(c)(1, 2, 3) ).toEqual [3, 2, 1] it "should flip a quaternary function", -> expect( flip(d)(1, 2, 3, 4) ).toEqual [4, 3, 2, 1] it "should flip a variadic function", -> expect( flip(v)(1, 2, 3, 4, 5) ).toEqual [5, 4, 3, 2, 1] it "should respect arities", -> expect( flip(v).length ).toEqual v.length expect( flip(a).length ).toEqual a.length expect( flip(b).length ).toEqual b.length expect( flip(c).length ).toEqual c.length # expect( flip(d).length ).toEqual d.length # NO; We do not guarantee arity for length > 3 it 'should be self-currying', -> expect( flip(b)(1)(2) ).toEqual [2, 1] expect( flip(c)(1)(2)(3) ).toEqual [3, 2, 1] expect( flip(d)(1)(2)(3)(4) ).toEqual [4, 3, 2, 1] ================================================ FILE: spec/decorating-classes.spec.coffee ================================================ {classDecorator, mixin, fluent} = require('../lib/allong.es.js').allong.es class Todo constructor: (name) -> self = if this instanceof Todo this else new Todo() self.name = name or 'Untitled' self.done = false do: fluent -> this.done = true undo: fluent -> this.done = false describe "features", -> it "should include mixin", -> expect(typeof mixin).toEqual 'function' it "should include classDecorator", -> expect(typeof classDecorator).toEqual 'function' describe "classDecorator", -> AndColourCoded = classDecorator setColourRGB: (r, g, b) -> @colourCode = { r, g, b } this getColourRGB: -> @colourCode ColourTodo = AndColourCoded Todo todo = new ColourTodo('Use More Decorators') .setColourRGB(0, 255, 0) it "should set the name correctly", -> expect( todo.name ).toEqual "Use More Decorators" it "should set the colour code correctly", -> expect( todo.getColourRGB() ).toEqual r: 0 g: 255 b: 0 describe "mixin", -> LocationAware = mixin setLocation: (@location) -> this getLocation: -> @location describe "Using context", -> it "should add location to the existing class's prototype", -> LocationAware.call(Todo.prototype) expect( typeof Todo.prototype.setLocation ).toEqual 'function' expect( typeof Todo.prototype.getLocation ).toEqual 'function' describe "Using a parameter", -> a = { a: 'a' } b = { b: 'b' } c = { c: 'b' } it "should not add the location to the context", -> LocationAware.call(a, b) expect( a.setLocation ).toBeUndefined() expect( a.getLocation ).toBeUndefined() expect( typeof b.setLocation ).toEqual 'function' expect( typeof b.getLocation ).toEqual 'function' it "should add the location when called normally", -> LocationAware(c) expect( typeof c.setLocation ).toEqual 'function' expect( typeof c.getLocation ).toEqual 'function' ================================================ FILE: spec/folding.spec.coffee ================================================ {mapWith, deepMapWith, filter} = require('../lib/allong.es.js').allong.es square = (n) -> n * n describe 'mapWith', -> it 'should square some numbers', -> expect( mapWith(square)([1..5]) ).toEqual [1, 4, 9, 16, 25] describe 'deepMapWith', -> it 'should square some numbers', -> expect( deepMapWith(square)([1, [2..4], 5]) ).toEqual [1, [4, 9, 16], 25] describe "filter", -> it "should find odd numbers", -> expect( filter([1, 2, 3, 4, 5, 6], '% 2 === 1') ).toEqual [1, 3, 5] it "should be self-currying", -> expect( filter([1, 2, 3, 4, 5, 6])('% 2 === 0') ).toEqual [2, 4, 6] ================================================ FILE: spec/iterators.spec.coffee ================================================ {iterators: {slice, drop, take, accumulate, accumulateWithReturn, fold, map, filter, FlatArrayIterator, RecursiveArrayIterator, unfold, unfoldWithReturn}} = require('../lib/allong.es.js').allong.es describe "FlatArrayIterator", -> it "should iterate over a flat array", -> i = FlatArrayIterator([1, 2, 3, 4, 5]) expect( i() ).toEqual(1) expect( i() ).toEqual(2) expect( i() ).toEqual(3) expect( i() ).toEqual(4) expect( i() ).toEqual(5) expect( i() ).toBeUndefined() it "should not iterate down through an array", -> i = FlatArrayIterator([1, [2, 3, [4]], 5]) expect( i() ).toEqual(1) expect( i() ).not.toEqual(2) expect( i() ).toEqual(5) expect( i() ).toBeUndefined() it "should have no values given an empty array", -> i = FlatArrayIterator([]) expect( i() ).toBeUndefined() it "should have a values given an empty tree", -> i = FlatArrayIterator([[], [[]]]) expect( i() ).not.toBeUndefined() describe "RecursiveArrayIterator", -> it "should have no values given an empty array", -> i = RecursiveArrayIterator([]) expect( i() ).toBeUndefined() it "should have no values given an empty tree", -> i = RecursiveArrayIterator([[], [[]]]) expect( i() ).toBeUndefined() it "should iterate over a flat array", -> i = RecursiveArrayIterator([1, 2, 3, 4, 5]) expect( i() ).toEqual(1) expect( i() ).toEqual(2) expect( i() ).toEqual(3) expect( i() ).toEqual(4) expect( i() ).toEqual(5) expect( i() ).toBeUndefined() it "should also iterate down through an array", -> i = RecursiveArrayIterator([1, [2, 3, [4]], 5]) expect( i() ).toEqual(1) expect( i() ).toEqual(2) expect( i() ).toEqual(3) expect( i() ).toEqual(4) expect( i() ).toEqual(5) expect( i() ).toBeUndefined() sum = (x, y) -> x + y describe "fold", -> describe "with a seed", -> it "should fold an iterator with many elements", -> expect( fold(RecursiveArrayIterator([1, [2, 3, [4]], 5]), sum, 0) ).toEqual(15) it "should fold an iterator with one element", -> expect( fold(RecursiveArrayIterator([[[4], []]]), sum, 42) ).toEqual(46) it "should fold an empty iterator", -> expect( fold(RecursiveArrayIterator([[], [[]]]), sum, 42) ).toEqual(42) describe "without a seed", -> it "should fold an array with two or more elements", -> expect( fold(RecursiveArrayIterator([1, [2, 3, [4]], 5]), sum) ).toEqual(15) it "should fold an array with one element", -> expect( fold(RecursiveArrayIterator([[[4], []]]), sum) ).toEqual(4) it "should fold an array with no elements", -> expect( fold(RecursiveArrayIterator([[[], []]]), sum) ).toBeUndefined() describe "accumulate", -> describe "with a seed", -> it "should map an iterator with many elements", -> i = accumulate(RecursiveArrayIterator([1, [2, 3, [4]], 5]), sum, 0) expect( i() ).toEqual(1) expect( i() ).toEqual(3) expect( i() ).toEqual(6) expect( i() ).toEqual(10) expect( i() ).toEqual(15) expect( i() ).toBeUndefined() it "should map an iterator with one element", -> i = accumulate(RecursiveArrayIterator([[[4], []]]), sum, 42) expect( i() ).toEqual(46) expect( i() ).toBeUndefined() it "should map an empty iterator", -> i = accumulate(RecursiveArrayIterator([[[], []]]), sum, 42) expect( i() ).toBeUndefined() describe "without a seed", -> it "should map an iterator with many elements", -> i = accumulate(RecursiveArrayIterator([1, [2, 3, [4]], 5]), sum) expect( i() ).toEqual(1) expect( i() ).toEqual(3) expect( i() ).toEqual(6) expect( i() ).toEqual(10) expect( i() ).toEqual(15) expect( i() ).toBeUndefined() it "should map an iterator with one element", -> i = accumulate(RecursiveArrayIterator([[[4], []]]), sum) expect( i() ).toEqual(4) expect( i() ).toBeUndefined() it "should map an empty iterator", -> i = accumulate(RecursiveArrayIterator([[[], []]]), sum) expect( i() ).toBeUndefined() square = (x) -> x*x describe "map", -> it "should map an iterator with many elements", -> i = map(RecursiveArrayIterator([1, [2, 3, [4]], 5]), square) expect( i() ).toEqual(1) expect( i() ).toEqual(4) expect( i() ).toEqual(9) expect( i() ).toEqual(16) expect( i() ).toEqual(25) expect( i() ).toBeUndefined() it "should map an iterator with one element", -> i = map(RecursiveArrayIterator([[[4], []]]), square) expect( i() ).toEqual(16) expect( i() ).toBeUndefined() it "should map an empty iterator", -> i = map(RecursiveArrayIterator([[[], []]]), square) expect( i() ).toBeUndefined() odd = (x) -> x % 2 is 1 describe "filter", -> it "should filter an iterator with many elements", -> i = filter(RecursiveArrayIterator([1, [2, 3, [4]], 5]), odd) expect( i() ).toEqual(1) expect( i() ).toEqual(3) expect( i() ).toEqual(5) expect( i() ).toBeUndefined() it "should filter an iterator with one element", -> i = filter(RecursiveArrayIterator([[[4], []]]), odd) expect( i() ).toBeUndefined() it "should filter an empty iterator", -> i = filter(RecursiveArrayIterator([[[], []]]), odd) expect( i() ).toBeUndefined() it "should filter an iterator with no matches", -> i = filter(FlatArrayIterator([2, 4, 6, 8, 10]), odd) expect( i() ).toBeUndefined() describe "slice", -> describe "with two parameter", -> it "should return an identity iterator", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 0) expect( i() ).toEqual 1 expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() it "should return a trailing iterator", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 1) expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() it "should return an empty iterator when out of range", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 5) expect( i() ).toBeUndefined() describe "with three parameters", -> it "should return an identity iterator", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 0, 5) expect( i() ).toEqual 1 expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 0, 99) expect( i() ).toEqual 1 expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() it "should return a leading iterator", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 0, 4) expect( i() ).toEqual 1 expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toBeUndefined() it "should return a trailing iterator", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 1, 4) expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 1, 99) expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() it "should return an inner iterator", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 1, 3) expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toBeUndefined() it "should return an empty iterator when given a zero length", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 1, 0) expect( i() ).toBeUndefined() it "should return an empty iterator when out of range", -> i = slice(FlatArrayIterator([1, 2, 3, 4, 5]), 5, 1) expect( i() ).toBeUndefined() describe "drop", -> it "should drop the number of items dropped", -> i = drop(FlatArrayIterator([1, 2, 3, 4, 5]), 2) expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() it "should handle overdropping", -> i = drop(FlatArrayIterator([1, 2, 3, 4, 5]), 99) expect( i() ).toBeUndefined() it "should handle underdropping", -> i = drop(FlatArrayIterator([1, 2, 3, 4, 5]), 0) expect( i() ).toEqual 1 expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() it "should default to one", -> i = drop(FlatArrayIterator([1, 2, 3, 4, 5])) expect( i() ).toEqual 2 expect( i() ).toEqual 3 expect( i() ).toEqual 4 expect( i() ).toEqual 5 expect( i() ).toBeUndefined() describe "accumulateWithReturn", -> it "should pass the state and result in a pair", -> i = accumulateWithReturn(FlatArrayIterator([1, 2, 3, 4, 5]), (state, element) -> [state + element, 'Total is ' + (state + element)] , 0); expect( i() ).toEqual 'Total is 1' expect( i() ).toEqual 'Total is 3' expect( i() ).toEqual 'Total is 6' expect( i() ).toEqual 'Total is 10' expect( i() ).toEqual 'Total is 15' describe "unfold", -> it "should unfold and include the seed", -> i = unfold 0, (n) -> n + 1 expect( i() ).toEqual 0 expect( i() ).toEqual 1 expect( i() ).toEqual 2 it "should not unfold without a seed", -> i = unfold undefined, (n) -> n + 1 expect( i() ).toEqual undefined expect( i() ).toEqual undefined expect( i() ).toEqual undefined expect( i() ).toEqual undefined describe "unfoldWithReturn", -> it "should unfold and throw off a value", -> i = unfoldWithReturn 1, (n) -> [n + 1, n*n] expect( i() ).toEqual 1 expect( i() ).toEqual 4 expect( i() ).toEqual 9 expect( i() ).toEqual 16 it "should halt if it returns undefined", -> i = unfoldWithReturn 1, (n) -> [n + 1, if n is 1 then undefined else n * n] expect( i() ).toEqual undefined expect( i() ).toEqual undefined expect( i() ).toEqual undefined expect( i() ).toEqual undefined it "should halt if the state becomes undefined", -> i = unfoldWithReturn 1, (n) -> [(if n is 3 then undefined else n + 1), (if n is undefined then 100 else n * n)] expect( i() ).toEqual 1 expect( i() ).toEqual 4 expect( i() ).toEqual 9 expect( i() ).toEqual undefined ================================================ FILE: spec/map-arguments-with.spec.coffee ================================================ {mapArgumentsWith, variadic, maybe} = require('../lib/allong.es').allong.es myArgs = variadic (a) -> a double = (n) -> n * 2 plus1 = (n) -> n + 1 describe "mapArgumentsWith", -> it "should map some simple integers", -> expect( mapArgumentsWith(double, myArgs)(1, 2, 3) ).toEqual [2, 4, 6] it "should curry", -> expect( mapArgumentsWith(double)(myArgs)(1, 2, 3) ).toEqual [2, 4, 6] it "should construct nice decorators", -> doubleYourArguments = mapArgumentsWith(double) expect( (doubleYourArguments myArgs)(1, 2, 3) ).toEqual [2, 4, 6] describe "self-mapping", -> describe "maybe", -> it "should return a function for a function", -> expect( maybe(double)(2) ).toEqual 4 expect( maybe(double)(undefined) ).toBeUndefined() it "should return an array for multiple items", -> expect( maybe(double, plus1)[0](4) ).toEqual 8 expect( maybe(double, plus1)[0](undefined) ).toBeUndefined() expect( maybe(double, plus1)[1](4) ).toEqual 5 expect( maybe(double, plus1)[1](undefined) ).toBeUndefined() ================================================ FILE: spec/sequence-objects.spec.coffee ================================================ {sequence, flatten} = require('../lib/allong.es').allong.es WithLogging = chain: (valueAndLogList, fn) -> value = valueAndLogList[0] logList = valueAndLogList[1] resultAndLogList = fn(value) result = resultAndLogList[0] resultLogList = flatten(logList.concat(resultAndLogList[1])) [result, resultLogList] of: (argument) -> [argument, []] double = (number) -> result = number * 2 [result, ['' + number + ' * 2 = ' + result]] plus1 = (number) -> result = number + 1 [result, ['' + number + ' + 1 = ' + result]] describe "sequence with object definitions", -> it "should work for some simple math", -> expect( sequence(WithLogging, double, plus1 )(2) ).toEqual [5, ['2 * 2 = 4', '4 + 1 = 5']] ================================================ FILE: spec/sequence.advanced.spec.coffee ================================================ {sequence, sequence: {Identity, Maybe, Writer, List, Then, Callback}} = require('../lib/allong.es.js').allong.es describe "Sequence", -> describe "Naive", -> double = (n) -> n + n plusOne = (n) -> n + 1 it "should be a thing", -> expect( sequence ).not.toBeNull() it "should return a function when given a function", -> expect( sequence(double) ).not.toBeNull() it "should do a single function", -> expect( sequence(double)(3) ).toEqual 6 it "should do two functions", -> expect( sequence(double, plusOne)(3) ).toEqual 7 describe "Identity", -> double = (n) -> n + n plusOne = (n) -> n + 1 it "should be a thing", -> expect( Identity ).not.toBeNull() it "should do a single function", -> expect( sequence(Identity, double)(3) ).toEqual 6 it "should do two functions", -> expect( sequence(Identity, double, plusOne)(3) ).toEqual 7 describe "Maybe", -> double = (n) -> n + n plusOne = (n) -> n + 1 it "should pass numbers through", -> expect( sequence(Maybe, double, plusOne)(3) ).toEqual 7 it "should pass null through", -> expect( sequence(Maybe, double, plusOne)(null) ).toBeNull() it "should pass undefined through", -> expect( sequence(Maybe, double, plusOne)(undefined) ).toBeUndefined() it "should short-circuit", -> expect( sequence(Maybe, double, ((x) ->), plusOne)(undefined) ).toBeUndefined() describe "Writer", -> parity = (n) -> [ n if n % 2 is 0 then 'even' else 'odd' ] space = (n) -> [ n ' ' ] size = (n) -> [ n if n < 10 then 'small' else 'normal' ] it "should accumulate writes", -> expect( sequence(Writer, parity, space, size)(5) ).toEqual [5, 'odd small'] describe 'List', -> oneToN = (n) -> [1..n] nToOne = (n) -> [n..1] it "should handle two levels of lists", -> expect( sequence(List, oneToN, nToOne)(3) ).toEqual [1, 2, 1, 3, 2, 1] ================================================ FILE: spec/sequence.callback.spec.coffee ================================================ {sequence, sequence: {Identity, Maybe, Writer, List, Then, Callback}} = require('../lib/allong.es.js').allong.es describe "Sequence", -> describe "Callback", -> identity = (v) -> v describe "with one parameter", -> double = (v, c) -> c(v * 2) plus1 = (v, c) -> c(v + 1) it "should work for a double", -> expect( sequence(Callback, double)(42, identity) ).toBe 84 it "should work for a double double", -> expect( sequence(Callback, double, double)(2, identity) ).toBe 8 it "should work for a double plus1 double", -> expect( sequence(Callback, double, plus1, double)(2, identity) ).toBe 10 describe "with multiple parameters", -> argsToArray = (args..., callback) -> callback(args) argsToArgs = (args..., callback) -> callback(args...) it "should work for a singleton", -> expect( sequence(Callback, argsToArgs, argsToArray)(1, identity) ).toEqual [1] it "should work for a doubleton", -> expect( sequence(Callback, argsToArgs, argsToArray)(1, 2, identity) ).toEqual [1, 2] ================================================ FILE: spec/sequence.decorataors.spec.coffee ================================================ {sequence, maybe, andand, oror} = require('../lib/allong.es.js').allong.es describe "Sequence Decorations", -> identity = (fn) -> fn identitywrapper = (fn) -> (value) -> fn(value) double = (n) -> n + n plusOne = (n) -> n + 1 nothing = (x) -> describe "maybe", -> it "for a number", -> expect( sequence( maybe( double, plusOne))(3) ).toEqual sequence(maybe(double), maybe(plusOne))(3) it "for a null", -> expect( sequence( maybe( double, plusOne))(null) ).toEqual sequence(maybe(double), maybe(plusOne))(null) it "for undefined", -> expect( sequence( maybe( double, plusOne))(undefined) ).toEqual sequence(maybe(double), maybe(plusOne))(undefined) it "should short-circuit", -> expect( sequence( maybe( double, nothing, plusOne))(10) ).toEqual sequence(maybe(double), maybe(nothing), maybe(plusOne))(10) ================================================ FILE: spec/sequence.then.spec.coffee ================================================ Promise = require 'promise' {sequence} = require('../lib/allong.es.js').allong.es # also demonstrates string shorthand describe "Then", -> double = (value) -> new Promise (resolve, reject) -> resolve(value * 2) success = undefined failure = undefined beforeEach -> success = undefined failure = undefined describe "for a doubling promise", -> it "should work asynchronously", (done) -> sequencedPromise = sequence('Then', double)(3) sequencedPromise.then ((value) -> success = value done()), ((reason) -> failure = reason done()) afterEach -> expect( success ).toEqual 6 expect( failure ).toBeUndefined() describe "for a double double", -> it "should work asynchronously", (done) -> sequencedPromise = sequence('Then', double, double)(2) sequencedPromise.then ((value) -> success = value done()), ((reason) -> failure = reason done()) afterEach -> expect( success ).toEqual 8 expect( failure ).toBeUndefined() describe "for a double fail double", -> it "should fail forward", (done) -> failer = (value) -> new Promise (resolve, reject) -> reject 'sorry, old chap' sequencedPromise = sequence('Then', double, failer, double)(2) sequencedPromise.then( ((value) -> success = value done()), ((reason) -> failure = reason done())) afterEach -> expect( success ).toBeUndefined() expect( failure ).toBe 'sorry, old chap' ================================================ FILE: spec/sequence.transformers.spec.coffee ================================================ Promise = require 'promise' {sequence, sequence: {Then, Callback, fn2Then, fn2Callback, callback2Then, then2Callback}} = require('../lib/allong.es.js').allong.es describe "fn2Then", -> plus1 = (value) -> value + 1 double = (value) -> new Promise (resolve, reject) -> resolve(value * 2) success = undefined failure = undefined beforeEach -> success = undefined failure = undefined describe "for a single function", -> it "should work ", (done) -> sequencedPromise = sequence(Then, double, fn2Then(plus1), double )(1) sequencedPromise.then ((value) -> success = value done()), ((reason) -> failure = reason done()) afterEach -> expect( success ).toEqual 6 expect( failure ).toBeUndefined() describe "callback2Then", -> plus1 = (value, callback) -> callback(value + 1) double = (value) -> new Promise (resolve, reject) -> resolve(value * 2) success = undefined failure = undefined beforeEach -> success = undefined failure = undefined describe "for a single function", -> it "should work ", (done) -> sequencedPromise = sequence(Then, double, callback2Then(plus1), double )(1) sequencedPromise.then ((value) -> success = value done()), ((reason) -> failure = reason done()) afterEach -> expect( success ).toEqual 6 expect( failure ).toBeUndefined() describe "fn2Callback", -> plus1 = (value) -> value + 1 double = (value, callback) -> callback(value * 2) identity = (n) -> n it "should work for a double plus1 double", -> expect( sequence(Callback, double, fn2Callback(plus1), double )(2, identity) ).toBe 10 describe "then2Callback", -> plus1 = (value) -> new Promise (resolve, reject) -> resolve(value + 1) double = (value, callback) -> callback(value * 2) identity = (n) -> n success = undefined failure = undefined beforeEach -> success = undefined failure = undefined describe "for a single function", -> it "should work ", (done) -> sequence(Callback, double, then2Callback(plus1), double )(2, (value) -> success = value done()) afterEach -> expect( success ).toEqual 10 expect( failure ).toBeUndefined() ================================================ FILE: spec/trampoline.spec.coffee ================================================ {trampoline, tailCall} = require('../lib/allong.es.js').allong.es describe "trampolining", -> depth = 32768 it "should execute a clean even/odd to #{depth} levels", -> even = trampoline (n) -> if n is 0 true else tailCall odd, n - 1 odd = trampoline (n) -> if n is 0 false else tailCall even, n - 1 expect( even(0) ).toEqual true expect( even(1) ).toEqual false expect( even(2) ).toEqual true expect( even(3) ).toEqual false expect(-> even(depth) ).not.toThrow() it "should allow tail calling a co-recursive non-trampolined function too", -> even2 = trampoline (n) -> if n is 0 true else tailCall odd2, n - 1 odd2 = (n) -> if n is 0 false else even2 n - 1 expect(-> even2(1000) ).not.toThrow() expect( even2(100) ).toEqual true expect( even2(101) ).toEqual false