├── .gitignore ├── index.js ├── package.json ├── lib ├── RegExp.js ├── Math.js ├── operators.js ├── Function.js └── Array.js ├── test.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | exports.Array = require("./lib/Array") 2 | exports.ops = 3 | exports.operators = require("./lib/operators") 4 | exports.Function = require("./lib/Function") 5 | exports.Math = require("./lib/Math") 6 | exports.RegExp = require("./lib/RegExp") -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jed Schmidt (http://jed.is)", 3 | "name": "emit", 4 | "description": "A reactive toolkit for JavaScript", 5 | "version": "0.0.2", 6 | "keywords": [ 7 | "functional", 8 | "reactive", 9 | "emitter", 10 | "emit" 11 | ], 12 | "homepage": "https://emitjs.org", 13 | "repository": { 14 | "type": "git", 15 | "url": "git://github.com/jed/emit.git" 16 | }, 17 | "engines": { 18 | "node": "~0.6.6" 19 | }, 20 | "dependencies": {}, 21 | "devDependencies": {} 22 | } -------------------------------------------------------------------------------- /lib/RegExp.js: -------------------------------------------------------------------------------- 1 | var apply = require("./Function").prototype.apply 2 | , original = /./.constructor 3 | , methods = ["exec", "toString", "test"] 4 | , i = methods.length 5 | 6 | function RegExp() { 7 | return apply.call(original, null, arguments) 8 | } 9 | 10 | while (i--) !function(name) { 11 | RegExp.prototype[name] = function() { 12 | var method = original[name] 13 | 14 | exports[name] = function() { 15 | return apply.call(method, this, Array.apply(null, arguments)) 16 | } 17 | } 18 | }(methods[i]) 19 | 20 | module.exports = RegExp -------------------------------------------------------------------------------- /lib/Math.js: -------------------------------------------------------------------------------- 1 | var apply = require("./Function").prototype.apply 2 | , methods, i 3 | 4 | methods = ["LN10", "PI", "E", "LOG10E", "SQRT2", "LOG2E", "SQRT1_2", "LN2"] 5 | i = methods.length 6 | 7 | while (i--) exports[methods[i]] = Math[methods[i]] 8 | 9 | methods = ["cos", "pow", "log", "tan", "sqrt", "ceil", "asin", "abs", "max", "exp", "atan2", "random", "round", "floor", "acos", "atan", "min", "sin"] 10 | i = methods.length 11 | 12 | while (i--) !function(name) { 13 | var method = Math[name] 14 | exports[name] = function(){ return apply.call(method, null, arguments) } 15 | }(methods[i]) -------------------------------------------------------------------------------- /lib/operators.js: -------------------------------------------------------------------------------- 1 | var apply = require("./Function").prototype.apply 2 | , operators = ["!0", "~0", "typeof 0", "void 0", "0*1", "0/1", "0%1", "0+1", "0-1", "0<<1", "0>>1", "0>>>1", "0<1", "0<=1", "0>1", "0>=1", "0==1", "0!=1", "0===1", "0!==1", "0&1", "0^1", "0|1", "0&&1", "0||1", "0,1", "0?1:2", "0[1]", "0 in 1"] 3 | , i = operators.length 4 | 5 | while (i--) !function(str) { 6 | var body = str.replace(/\d/g, "arguments[$&]") 7 | , name = str.replace(/\d| /g, "") 8 | , fn = Function("return " + body) 9 | 10 | exports[name] = function(){ return apply.call(fn, null, arguments) } 11 | }(operators[i]) -------------------------------------------------------------------------------- /lib/Function.js: -------------------------------------------------------------------------------- 1 | var Array = require("./Array") 2 | , ArrayCtor = [].constructor 3 | , FunctionCtor = ArrayCtor.constructor 4 | , concat = ArrayCtor.prototype.concat 5 | , proto = Function.prototype 6 | , call 7 | , apply 8 | , _new 9 | 10 | function Function() { 11 | return apply.call(FunctionCtor, null, arguments) 12 | } 13 | 14 | call = proto.call = function call() { 15 | var args = Array.apply(null, arguments) 16 | , fn = this 17 | 18 | return typeof args != "function" ? 19 | 20 | fn.call.apply(fn, args) : 21 | 22 | function(emit) { 23 | args(function(err, data) { 24 | if (!err) data = fn.call.apply(fn, data) 25 | return emit(err, data) 26 | }) 27 | } 28 | } 29 | 30 | apply = proto.apply = function(context, args) { 31 | var fn = this 32 | 33 | return typeof args != "function" ? 34 | 35 | call.apply(fn, concat.apply([context], args)) : 36 | 37 | function(emit) { 38 | args(function(err, data) { 39 | if (!err) data = apply.call(fn, context, data) 40 | return emit(err, data) 41 | }) 42 | } 43 | } 44 | 45 | _new = proto["new"] = function() { 46 | var self = this 47 | , args = Array.apply(null, arguments) 48 | 49 | function getFn(arity) { 50 | return _new[arity] || (_new[arity] = Function( 51 | "Ctor, args, i", 52 | "return new Ctor(" + 53 | ArrayCtor(arity + 1).join(",args[i++]").slice(1) + 54 | ")" 55 | )) 56 | } 57 | 58 | return typeof args != "function" ? 59 | 60 | getFn(args.length)(self, args, 0) : 61 | 62 | function(emit) { 63 | args(function(err, data) { 64 | if (!err) data = getFn(data.length)(self, data, 0) 65 | return emit(err, data) 66 | }) 67 | } 68 | } 69 | 70 | module.exports = Function -------------------------------------------------------------------------------- /lib/Array.js: -------------------------------------------------------------------------------- 1 | var original = [].constructor 2 | , originalProto = original.prototype 3 | , arrayProto = Array.prototype 4 | , slice = originalProto.slice 5 | , methods 6 | , apply 7 | , i 8 | 9 | function Array() { 10 | var args = arguments 11 | , length = args.length 12 | , i = length 13 | 14 | while (i--) if (typeof args[i] == "function") break 15 | 16 | return i < 0 ? slice.call(args) : 17 | 18 | function(emit) { 19 | var evaluated = [] 20 | , remaining = length 21 | , i = 0 22 | 23 | while (i < length) !function(i) { 24 | !function set(err, data) { 25 | if (!err) { 26 | if (typeof data == "function") return data(set) 27 | if (remaining && !(i in evaluated)) remaining-- 28 | evaluated[i] = data 29 | } 30 | 31 | if (!remaining || err) return emit(err, evaluated.slice(0)) 32 | }(null, args[i]) 33 | }(i++) 34 | } 35 | } 36 | 37 | module.exports = Array 38 | 39 | apply = require("./Function").prototype.apply 40 | 41 | methods = ["concat", "indexOf", "join", "toString", "splice", "shift", "unshift", "lastIndexOf", "reverse", "pop", "push", "slice"] 42 | i = methods.length 43 | 44 | while (i--) !function(name) { 45 | arrayProto[name] = function() { 46 | return apply.call( 47 | originalProto[name], 48 | apply.call(Array, null, this), 49 | Array.apply(null, arguments) 50 | ) 51 | } 52 | }(methods[i]) 53 | 54 | methods = ["map", "sort", "filter", "some", "reduceRight", "forEach", "reduce", "every"] 55 | i = methods.length 56 | 57 | while (i--) !function(name) { 58 | arrayProto[name] = function() { 59 | var args = arguments 60 | 61 | return apply.call( 62 | function(){ return originalProto[name].apply(this, args) }, 63 | apply.call(Array, null, this) 64 | ) 65 | } 66 | }(methods[i]) -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | emit = require("emit") 2 | 3 | // emit method APIs are identical to their native counterparts... 4 | Math.pow(2, 2) // 4 5 | emit.Math.pow(2, 2) // 4 6 | 7 | Array(1, 2, 3) // [1, 2, 3] 8 | emit.Array(1, 2, 3) // [1, 2, 3] 9 | 10 | 1 / 2 // .5 11 | emit.ops["/"](1, 2) // .5 12 | 13 | // with one exception: emit methods can also take functions, which are 14 | // treated as yet-unevaluated values that can be "watched". All emit 15 | // methods compose these (possibly unevaluated) values into a single 16 | // value; if any of these values are functions, the returned value will 17 | // also be a function. 18 | 19 | // Here's an example of such a function; it reports an incremented number 20 | // back to its listener every second. 21 | function count(cb) { 22 | var send = function(){ cb(null, i++) } 23 | , i = 0 24 | 25 | send() 26 | setInterval(send, 1000) 27 | } 28 | 29 | // Since the value reported by count changes every second, so does 30 | // the value of any method that uses it. So these are all functions: 31 | emit.Math.pow(2, count) // [Function] 32 | emit.Array(1, 2, count) // [Function] 33 | emit.ops["/"](1, count) // [Function] 34 | 35 | // We can observe these changing values through the console. 36 | function log(e, data){ console.log(e || data) } 37 | 38 | emit.Math.pow(2, count)(log) // 1, 2, 4, 8, 16, 32... 39 | emit.Array(1, 2, count)(log) // [1, 2, 0], [1, 2, 1], [1, 2, 2]... 40 | emit.ops["/"](1, count)(log) // Infinity, 1, .5, .25... 41 | 42 | // Now we can now start to compose these into increasingly 43 | // complex values. 44 | random = emit.Math.random(count) 45 | randBin = emit.Math.round(random) 46 | coinToss = emit.ops["?:"](randBin, "heads", "tails") 47 | message = emit.ops["+"]("The coin landed on ", coinToss) 48 | 49 | message(log) // "The coin landed on heads" 50 | // "The coin landed on tails" 51 | // "The coin landed on heads" 52 | // ... -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ( ( (emit) ) ) 2 | ================ 3 | 4 | (emit) is a [functional reactive programming][frp] toolkit based on JavaScript primitives, allowing you to compose functional applications using the APIs you already know. 5 | 6 | (emit) is a very rough work in progress. It can run both in the browser and on [node.js][node]. 7 | 8 | Example 9 | ------- 10 | 11 | ```javascript 12 | emit = require("emit") 13 | 14 | // emit method APIs are identical to their native counterparts... 15 | Math.pow(2, 2) // 4 16 | emit.Math.pow(2, 2) // 4 17 | 18 | Array(1, 2, 3) // [1, 2, 3] 19 | emit.Array(1, 2, 3) // [1, 2, 3] 20 | 21 | 1 / 2 // .5 22 | emit.ops["/"](1, 2) // .5 23 | 24 | // with one exception: emit methods can also take functions, which are 25 | // treated as yet-unevaluated values that can be "watched". All emit 26 | // methods compose these (possibly unevaluated) values into a single 27 | // value; if any of these values are functions, the returned value will 28 | // also be a function. 29 | 30 | // Here's an example of such a function; it reports an incremented number 31 | // back to its listener every second. 32 | function count(cb) { 33 | var send = function(){ cb(null, i++) } 34 | , i = 0 35 | 36 | send() 37 | setInterval(send, 1000) 38 | } 39 | 40 | // Since the value reported by count changes every second, so does 41 | // the value of any method that uses it. So these are all functions: 42 | emit.Math.pow(2, count) // [Function] 43 | emit.Array(1, 2, count) // [Function] 44 | emit.ops["/"](1, count) // [Function] 45 | 46 | // We can observe these changing values through the console. 47 | function log(e, data){ console.log(e || data) } 48 | 49 | emit.Math.pow(2, count)(log) // 1, 2, 4, 8, 16, 32... 50 | emit.Array(1, 2, count)(log) // [1, 2, 0], [1, 2, 1], [1, 2, 2]... 51 | emit.ops["/"](1, count)(log) // Infinity, 1, .5, .25... 52 | 53 | // Now we can now start to compose these into increasingly 54 | // complex values. 55 | random = emit.Math.random(ping) 56 | randBin = emit.Math.round(random) 57 | coinToss = emit.ops["?:"](randBin, "heads", "tails") 58 | message = emit.ops["+"]("The coin landed on ", coinToss) 59 | 60 | message(log) // "The coin landed on heads" 61 | // "The coin landed on tails" 62 | // "The coin landed on heads" 63 | // ... 64 | ``` 65 | 66 | Installation 67 | ------------ 68 | 69 | To install, head to your terminal and enter: 70 | 71 | npm install emit 72 | 73 | Support 74 | ------- 75 | 76 | ### Current 77 | 78 | - `Array`: all ES3/ES5 methods 79 | - `Function`: `apply`, and `call` 80 | - `Math`: all statics and function 81 | - operators: most accessors, including `!`, `~`, `typeof`, `void`, `*`, `/`, `%`, `+`, `-`, `<<`, `>>`, `>>>`, `<`, `<=`, `>`, `>=`, `==`, `!=`, `===`, `!==`, `&`, `^`, `|`, `&&`, `||`, `, `, `?:`, `[]`, and `in` 82 | 83 | ### Planned 84 | 85 | - RegExp, String, Date, Number, Boolean 86 | - Function::bind 87 | - DOM/HTML 88 | - DOM events 89 | - JSON 90 | - node.js file system, etc. 91 | 92 | License 93 | ------- 94 | 95 | Copyright (c) 2012 Jed Schmidt, http://jed.is/ 96 | 97 | Permission is hereby granted, free of charge, to any person obtaining 98 | a copy of this software and associated documentation files (the 99 | "Software"), to deal in the Software without restriction, including 100 | without limitation the rights to use, copy, modify, merge, publish, 101 | distribute, sublicense, and/or sell copies of the Software, and to 102 | permit persons to whom the Software is furnished to do so, subject to 103 | the following conditions: 104 | 105 | The above copyright notice and this permission notice shall be 106 | included in all copies or substantial portions of the Software. 107 | 108 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 109 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 110 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 111 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 112 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 113 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 114 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 115 | 116 | [frp]: http://en.wikipedia.org/wiki/Functional_reactive_programming 117 | [node]: http://nodejs.org/ 118 | [flapjax]: http://www.flapjax-lang.org/ 119 | [tangle]: http://worrydream.com/Tangle/ 120 | [spreadsheet]: http://en.wikipedia.org/wiki/Spreadsheet --------------------------------------------------------------------------------