├── .gitignore ├── .travis.yml ├── samples ├── declaring_in_a_library.test.js ├── zip_with.test.js ├── map_async.test.js ├── map.test.js ├── reverse_list_with_reduce.test.js ├── readme.test.js ├── nodetuts.test.js ├── filter_with_error.test.js ├── two_patterns.js └── hof.js ├── package.json ├── test └── index.js ├── pattern.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | *.min.js -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: "node_js" 2 | node_js: 3 | - 0.4 4 | - 0.6 5 | branches: 6 | only: 7 | - master -------------------------------------------------------------------------------- /samples/declaring_in_a_library.test.js: -------------------------------------------------------------------------------- 1 | /* [ 2, 4, 6 ] */ 2 | var map = require('./hof').map; 3 | 4 | map(function duplicate(x) { return x*2; }, [1,2,3], [], 5 | function (x) { console.error(x); }); -------------------------------------------------------------------------------- /samples/zip_with.test.js: -------------------------------------------------------------------------------- 1 | /* [ '1:2', '3:4', '5:6' ] */ 2 | var zip = require('./hof').zip_with; 3 | 4 | zip(function concat(a,b) { return a + ':' + b; }, 5 | [1,3,5], [2,4,6,8], [], function (ac) { 6 | console.error(ac); 7 | }); -------------------------------------------------------------------------------- /samples/map_async.test.js: -------------------------------------------------------------------------------- 1 | /* [ 2, 4, 6 ] */ 2 | var map_async = require('./hof').map_async; 3 | 4 | function duplicate(x, cb) { 5 | setTimeout(function() { cb(x*2); }, Math.ceil(Math.random() * 100)); 6 | } 7 | 8 | map_async(duplicate, [1,2,3], [], function(ac) { console.error(ac); }); -------------------------------------------------------------------------------- /samples/map.test.js: -------------------------------------------------------------------------------- 1 | /* [ 2, 3, 4 ] */ 2 | var map = require('../pattern')() 3 | , _, f, ac 4 | ; 5 | 6 | map(f, [], ac, function done(_, _, ac) { return console.error(ac); }); 7 | map(f, _, ac, function all(f, l, ac) { 8 | ac.push(f(l.shift())); // head 9 | map(f, l, ac); // l is now tail 10 | }); 11 | 12 | map(function plusone(x) { return x+1; }, [1,2,3], []); -------------------------------------------------------------------------------- /samples/reverse_list_with_reduce.test.js: -------------------------------------------------------------------------------- 1 | /* [ 1, 2, 3, 4, 5 ] */ 2 | var reduce = require('./hof').foldl; 3 | 4 | function id(z, x, cb) { 5 | setTimeout( 6 | function() {z.unshift(x); cb(z);}, Math.ceil(Math.random() * 100)); } 7 | 8 | function reserve (list, cb) { reduce(id, [], list, function(z){ cb(z); }); } 9 | 10 | reserve([5,4,3,2,1], function (list) { console.error(list); }); -------------------------------------------------------------------------------- /samples/readme.test.js: -------------------------------------------------------------------------------- 1 | /* CAT */ 2 | var insert_all = require('../pattern')(), _; 3 | 4 | function insert_element(data, callback) { 5 | setTimeout(function() { callback(data); }, Math.ceil(Math.random() * 10)); 6 | } 7 | 8 | insert_all([], function () { console.error('CAT'); }); 9 | insert_all(_, function (l) { 10 | insert_element(l.shift(), function (elem) { 11 | console.log('‣ ', elem); 12 | insert_all(l); 13 | }); 14 | }); 15 | 16 | insert_all([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { "name" : "p" 2 | , "description" : "pattern matching in javascript for asyncronous iteration" 3 | , "author" : "nuno job (http://nunojob.com/)" 4 | , "version" : "0.2.0" 5 | , "main" : "./pattern.js" 6 | , "homepage" : "https://github.com/dscape/p" 7 | , "repository" : { "type": "git", "url": "http://github.com/dscape/p.git" } 8 | , "bugs" : "http://github.com/dscape/p/issues" 9 | , "keywords" : ["pattern matching", "pattern", "flow", "async"] 10 | , "engines" : { "node" : ">=0.4.0" } 11 | , "scripts" : { "test": "node test"} 12 | } -------------------------------------------------------------------------------- /samples/nodetuts.test.js: -------------------------------------------------------------------------------- 1 | /* done */ 2 | var insert_all = require('../pattern')(), _; 3 | 4 | // pretending we are doing an async call 5 | function insert_element(data, callback) { 6 | setTimeout(function() { callback(data); }, 7 | Math.ceil(Math.random() * 100)); 8 | } 9 | 10 | insert_all([], _, function stop(l,cb) { cb(); }); 11 | insert_all(_, _, function catchall(l, cb) { 12 | insert_element(l.shift(), function elem_cb(elem) { 13 | console.log(elem + ' inserted'); 14 | insert_all(l, cb); 15 | }); 16 | }); 17 | 18 | insert_all([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 19 | function done() { console.error('done'); }); -------------------------------------------------------------------------------- /samples/filter_with_error.test.js: -------------------------------------------------------------------------------- 1 | /* yeah! */ 2 | var filter = require('./hof').filter_async; 3 | 4 | // simulating an async function that could throw 5 | function gotBang(x, cb) { 6 | setTimeout(function() { 7 | try { 8 | if (x.msg.indexOf('!') !== 1) { 9 | process.stderr.write(x.msg); 10 | cb(null, x); } 11 | } catch (e) { cb(e); } 12 | }, Math.ceil(Math.random() * 300)); 13 | } 14 | 15 | filter(gotBang, 16 | [ {msg: "y"} 17 | , {msg: "e"} 18 | , {msg: "a"} 19 | , {msg: "h"} 20 | , {msg: "!"} 21 | , null 22 | , {msg: "?"} 23 | ], [], function(err,data) { 24 | if(!err) { console.error('I will never get executed!'); } }); 25 | -------------------------------------------------------------------------------- /samples/two_patterns.js: -------------------------------------------------------------------------------- 1 | var p1 = require('../pattern')() 2 | , p2 = require('../pattern')() 3 | , _ 4 | ; 5 | 6 | // pretending we are doing an async call 7 | function insert_element(data, callback) { 8 | setTimeout(function() { callback(data); }, 9 | Math.ceil(Math.random() * 1000)); 10 | } 11 | 12 | function done(l) { console.log('done'); } 13 | 14 | function any_generator (name,context) { 15 | return function (l) { 16 | insert_element(l.shift(), function(elem) { 17 | console.log(elem + ' inserted in ' + name); 18 | context(l); 19 | }); 20 | }; 21 | } 22 | 23 | p1([], done); 24 | p1(_, any_generator('p1', p1)); 25 | p2([], done); 26 | p2(_, any_generator('p2', p2)); 27 | 28 | p1([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 29 | p2([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | , assert = require('assert') 3 | , path = __dirname + '/../samples/' 4 | , files = fs.readdirSync(path) 5 | , spawn = require('child_process').spawn 6 | , colors 7 | , return_code = 0 8 | , i = 0 9 | ; 10 | 11 | try { colors = require('colors'); } catch (e) {} 12 | function c(msg,color) { return colors ? msg[color] : msg; } 13 | 14 | var tests = files.filter(function (f) { return f.indexOf('.test.') !== -1; }); 15 | var expected = tests.length; 16 | 17 | tests.forEach( 18 | function (f) { 19 | var contents = fs.readFileSync(path + f, 'utf-8') 20 | , output = /\/\*\s*(.*?)\s*\*\//.exec(contents)[1] 21 | , node = spawn('node', [path + f]) 22 | , buffer = '' 23 | ; 24 | node.stderr.on('data', function(data) { buffer += data; }); 25 | node.on('exit', function() { 26 | i++; 27 | buffer = buffer.replace(/^\s+|\s+$/g,""); 28 | var ok = buffer === output; 29 | console.log(c(ok ? '✔' : '✗', ok ? 'green' : 'red'), 'samples/'+f); 30 | if(!ok) { 31 | return_code=1; 32 | console.log(c(' ♯', 'cyan'), buffer); 33 | console.log(c(' δ', 'magenta'), output); 34 | } 35 | if(i===expected) { 36 | process.exit(return_code); 37 | } 38 | }); 39 | } 40 | ); -------------------------------------------------------------------------------- /pattern.js: -------------------------------------------------------------------------------- 1 | (function () { // stack refers to registered patterns 2 | var DEBUG = typeof process !== 'undefined' && process.env.DEBUG; 3 | function log() { if(DEBUG) console.log.apply(this,arguments); } 4 | function match(pattern, val) { 5 | if(pattern === Error) { return val && val.name.indexOf('Error') !== -1; } 6 | if(pattern === undefined) return true; 7 | if(typeof pattern === 'object') 8 | return JSON.stringify(pattern) === JSON.stringify(val); 9 | return pattern.toString() === val.toString(); } 10 | function p() { var stack = [], arity; // stack keeps stack of patterns 11 | return function () { 12 | if(!arity) { arity = arguments.length-1; } // set arity in first invok. 13 | if(arity===arguments.length) { // # arguments match arity, execute 14 | var j=0, i=0; // we need explicit control over vars 15 | ol: for(; i i) { // if there's something in this pos for pattern 23 | if(match(s[i], arguments[i])) { // if we have a match 24 | log(' ✔ ', s[i], '===', arguments[i]); // not last v in patt? 25 | if(arguments.length !== i+1) { log(' ⥁'); continue ol; } } 26 | else { // if it doesnt match try next pattern in stack 27 | log(' ✗ ', s[i], '===', arguments[i]); 28 | i=0; 29 | if(stack.length!==j+1) { log(' ⥁'); continue; } } } 30 | var f = s[s.length-1]; 31 | log(' ' + (typeof f === 'function' ? 'ƒ' : 'λ'), f.name || f); 32 | // execute whatever is the last argument on last pattern of stack 33 | return (typeof f === 'function') ? // is there a callback? 34 | f.apply(this, arguments) : null; } } 35 | } else { 36 | stack.push([].slice.call(arguments,0)); // initializing add pattern 37 | log('‣ ', [].slice.call(arguments,0)); 38 | } }; } 39 | typeof exports === 'undefined' ? (window.pattern=p) : (module.exports = p); 40 | })(); -------------------------------------------------------------------------------- /samples/hof.js: -------------------------------------------------------------------------------- 1 | var p = require('../pattern') 2 | , map = p() 3 | , mapa = p() 4 | , zip_with = p() 5 | , filtera = p() 6 | , maybe = p() 7 | , foldl = p() 8 | , _, f, ac, l, l1, l2, cb, errcb, z 9 | ; 10 | 11 | // error handling for conditional execution of functions 12 | maybe(Error, _, errcb, cb, function (err, data, errcb, cb) { errcb(err); }); 13 | maybe(_, _, errcb, cb, function(err, data, errcb, cb) { cb(data); }); 14 | 15 | // map 16 | // map _ [] = [] 17 | // map f (x:xs) = f x : map f xs 18 | map(_, [], ac, cb, 19 | function map_done(f, l, ac, cb) { return cb(ac); }); 20 | map(f, l, ac, cb, 21 | function map_catch_all(f, l, ac, cb) { 22 | ac.push(f(l.shift())); // head 23 | map(f, l, ac, cb); // l is now tail 24 | }); 25 | 26 | // map with async f, no error possible 27 | mapa(f, [], ac, cb, 28 | function map_done(f, l, ac, cb) { return cb(ac); }); 29 | mapa(f, l, ac, cb, 30 | function map_catch_all(f, l, ac, cb) { 31 | f(l.shift(), function(x) { 32 | ac.push(x); 33 | mapa(f, l, ac, cb); // l is now tail 34 | }); 35 | }); 36 | 37 | // filter with async f 38 | // filter _ [] = [] 39 | // filter p (x:xs) 40 | // | p x = x : filter p xs 41 | // | otherwise = filter p xs 42 | filtera(_, [], ac, cb, function filter_done(f, l, ac, cb) { 43 | return cb(null, ac); 44 | }); 45 | filtera(f, l, ac, cb, function filter_catch_all(f, l, ac, cb) { 46 | var head = l.shift(); 47 | f(head, function(err, ok) { 48 | maybe(err, ok, cb, function (ok) { 49 | if (ok) ac.push(head); 50 | filtera(f, l, ac, cb); // l is now tail 51 | }); 52 | }); 53 | }); 54 | 55 | // foldl with async f, no errors possible 56 | // foldl f z [] = z 57 | // foldl f z (x:xs) = foldl f (f z x) xs 58 | foldl(f, z, [], cb, function foldl_done(f, z, l, cb) { cb(z); }); 59 | foldl(f, z, l, cb, function foldl_all(f, z, l, cb) { 60 | f(z, l.shift(), function foldl_f_async(new_z) { 61 | foldl(f, new_z, l, cb); 62 | }); 63 | }); 64 | 65 | // zipWith _ [] _ = [] 66 | // zipWith _ _ [] = [] 67 | // zipWith f (x:xs) (y:ys) = f x y : zipWith' f xs ys 68 | zip_with(_, [], _, ac, cb, function emptyl1(f, l1, l2, ac, cb) { cb(ac); }); 69 | zip_with(_, _, [], ac, cb, function emptyl2(f, l1, l2, ac, cb) { cb(ac); }); 70 | zip_with(f, l1, l2, ac, cb, function all(f, l1, l2, ac, cb) { 71 | ac.push(f(l1.shift(), l2.shift())); 72 | zip_with(f, l1, l2, ac, cb); 73 | }); 74 | 75 | module.exports = 76 | { map: map, map_async: mapa, zip_with: zip_with, filter_async: filtera 77 | , foldl: foldl }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pattern 2 | 3 | `pattern` is a way to do pattern matching in javascript that helps you with asynchronous iterations 4 | 5 | ``` js 6 | var map = require('../pattern')() 7 | , _, f, ac 8 | ; 9 | 10 | map(f, [], ac, function done(_, _, ac) { return console.error(ac); }); 11 | map(f, _, ac, function all(f, l, ac) { 12 | ac.push(f(l.shift())); // head 13 | map(f, l, ac); // l is now tail 14 | }); 15 | 16 | map(function plusone(x) { return x+1; }, [1,2,3], []); 17 | ``` 18 | 19 | ## explanation 20 | 21 | ``` js 22 | // check `samples/nodetuts.js` for working code 23 | insert_all([], function () { console.log('done'); }); 24 | insert_all(_, function (l) { 25 | insert_element(l.shift(), function (elem) { 26 | console.log('‣ ', elem); 27 | insert_all(l); 28 | }); 29 | }); 30 | 31 | insert_all([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 32 | ``` 33 | 34 | the first pattern in `pattern` sets the arity of the function to execute 35 | 36 | ``` js 37 | // first call sets arity #1 38 | // when this condition is met it logs the message done 39 | insert_all([], function () { console.log('done'); }); 40 | ``` 41 | 42 | then we normally register the iteration pattern 43 | 44 | ``` js 45 | // var _; was set in the top, value is undefined 46 | insert_all(_, function (l) { 47 | ``` 48 | 49 | if you then call `insert_all` where the argument count matches arity, `pattern` knows its time to execute 50 | 51 | ``` js 52 | // one argument, arity #1 53 | // run forest, run 54 | insert_all([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 55 | ``` 56 | 57 | [this] is the code you would normally write to do the same thing in javascript 58 | 59 | # installation 60 | 61 | ## node.js 62 | 63 | 1. install [npm] 64 | 2. `npm install p` 65 | 3. `var p = require('p');` 66 | 67 | # samples 68 | 69 | there are samples in the `samples` directory. check them out 70 | 71 | # roadmap 72 | 73 | [pointfree] style (**note** i'm just kidding) 74 | 75 | # contribute 76 | 77 | everyone is welcome to contribute. patches, bug-fixes, new features 78 | 79 | 1. create an [issue][issues] so the community can comment on your idea 80 | 2. fork `pattern` 81 | 3. create a new branch `git checkout -b feature_name` 82 | 4. create tests for the changes you made 83 | 5. make sure you pass both existing and newly inserted tests 84 | 6. commit your changes 85 | 7. push to your branch `git push origin feature_name` 86 | 8. create an pull request 87 | 88 | # meta 89 | 90 | * code: `git clone git://github.com/dscape/p.git` 91 | * home: 92 | * bugs: 93 | * build: [![build status](https://secure.travis-ci.org/dscape/p.png)](http://travis-ci.org/dscape/pattern) 94 | 95 | `(oO)--',-` in [caos] 96 | 97 | # license 98 | 99 | copyright 2012 nuno job `(oO)--',--` 100 | 101 | licensed under the apache license, version 2.0 (the "license"); 102 | you may not use this file except in compliance with the license. 103 | you may obtain a copy of the license at 104 | 105 | http://www.apache.org/licenses/LICENSE-2.0 106 | 107 | unless required by applicable law or agreed to in writing, software 108 | distributed under the license is distributed on an "as is" basis, 109 | without warranties or conditions of any kind, either express or implied. 110 | see the license for the specific language governing permissions and 111 | limitations under the license 112 | 113 | [npm]: http://npmjs.org 114 | [issues]: http://github.com/dscape/p/issues 115 | [caos]: http://caos.di.uminho.pt/ 116 | [samples]: https://github.com/dscape/p/tree/master/samples 117 | [this]: https://gist.github.com/00663e475092e55ac66c#file_howitis.js 118 | [pointfree]: http://www.haskell.org/haskellwiki/Pointfree 119 | --------------------------------------------------------------------------------