function-composer
Advanced tools
Comparing version 0.1.0 to 0.2.0
@@ -1,139 +0,91 @@ | ||
var compose = require('./../index'); | ||
var type = require('./../lib/types').type; | ||
var compose = require('../function-composer'); | ||
var I_MAX = 1e7; | ||
var I_MAX = 1e6; | ||
var count = 0; | ||
function f() { | ||
return count++; | ||
} | ||
var fns = { | ||
'number': f, | ||
'number,boolean': f, | ||
'number,number': f, | ||
'number,date': f, | ||
'string': f, | ||
'string,boolean': f | ||
}; | ||
var fn1 = compose(fns); | ||
function benchmark(name, test) { | ||
var start = +new Date(); | ||
var repetitions = test(); | ||
var end = +new Date(); | ||
var duration = end - start; | ||
function fOptimal1(arg0, arg1) { | ||
switch(type(arg0)) { | ||
case 'number': | ||
switch(type(arg1)) { | ||
case 'boolean': return count++; | ||
case 'number': return count++; | ||
case 'date': return count++; | ||
default: return count++; | ||
} | ||
console.log(name + ': ' + repetitions.toExponential() + ' calls, ' + | ||
Math.round(duration) + ' ms, ' + | ||
parseFloat((duration / repetitions).toPrecision(4)).toExponential() + ' ms per call'); | ||
case 'string': | ||
switch(type(arg1)) { | ||
case 'boolean': return count++; | ||
default: return count++; | ||
} | ||
} | ||
return { | ||
repetitions: repetitions, | ||
duration: duration | ||
}; | ||
} | ||
// interesting: not using local variables and no calling of type() seems to be fastest | ||
function fOptimal2(arg0, arg1) { | ||
switch(typeof(arg0)) { | ||
case 'number': | ||
if (typeof arg1 === 'boolean') { | ||
return count++; | ||
} | ||
if (typeof arg1 === 'number') { | ||
return count++; | ||
} | ||
//var count = 0; | ||
//function direct() { | ||
// var args = Array.prototype.slice.apply(arguments); | ||
// args.forEach(function (arg) { | ||
// count = count + (arg && arg.length) ? arg.length : 1; | ||
// }); | ||
// return count; | ||
//} | ||
if (arg1 instanceof Date) { | ||
return count++; | ||
} | ||
return count++; | ||
case 'string': | ||
switch(typeof(arg1)) { | ||
case 'boolean': return count++; | ||
default: return count++; | ||
} | ||
function direct(text) { | ||
var reverse = ''; | ||
var i = text.length; | ||
while (i > 0) { | ||
reverse += text.substring(i-1, i); | ||
i--; | ||
} | ||
return reverse; | ||
} | ||
// No worry about inlining the function, it's not faster than calling the function | ||
// Using if instead of switch is way faster though! | ||
// For some weird reason this is even faster than none. How's that possible? | ||
function fOptimal3(arg0, arg1) { | ||
if (typeof arg0 === 'number') { | ||
if (typeof arg1 === 'boolean') { | ||
return f(arg0, arg1); | ||
} | ||
//var count = 0; | ||
//function direct() { | ||
// return count++; | ||
//} | ||
if (typeof arg1 === 'number') { | ||
return f(arg0, arg1); | ||
} | ||
var composed = compose({ | ||
'number': direct, | ||
'number,boolean': direct, | ||
'number,number': direct, | ||
'number,date': direct, | ||
'string': direct, | ||
'string,boolean': direct | ||
}); | ||
if (arg1 instanceof Date) { | ||
return f(arg0, arg1); | ||
} | ||
return f(arg0); | ||
//console.log(composed.toString()) | ||
var directResult = benchmark('Direct', function () { | ||
var i, r, d = new Date(); | ||
for (i = 0; i < I_MAX; i++) { | ||
r = direct(1, d); | ||
r = direct('hello you there', false); | ||
r = direct(2, 4); | ||
} | ||
if (typeof arg0 === 'string') { | ||
if (typeof arg1 === 'boolean') { | ||
return f(arg0, arg1); | ||
} | ||
return I_MAX * 3; | ||
}); | ||
return f(arg0); | ||
var composedResult = benchmark('Composed', function () { | ||
var i, r, d = new Date(); | ||
for (i = 0; i < I_MAX; i++) { | ||
r = composed(1, d); | ||
r = composed('hello you there', false); | ||
r = composed(2, 4); | ||
} | ||
} | ||
return I_MAX * 3; | ||
}); | ||
var i, r, d = new Date(); | ||
console.time('none'); | ||
for (i = 0; i < I_MAX; i++) { | ||
r = f(1, d); | ||
r = f('hi', false); | ||
r = f(2, 4); | ||
} | ||
console.timeEnd('none'); | ||
var overhead = ((composedResult.duration - directResult.duration) / composedResult.repetitions); | ||
var percentage = overhead / (directResult.duration / directResult.repetitions) * 100; | ||
console.log('Overhead: ' + percentage.toPrecision(4) + '%, ' + | ||
parseFloat(overhead.toPrecision(4)).toExponential() + ' ms per call'); | ||
// Output is for example: | ||
// Direct: 3e+6 calls, 1865 ms, 6.217e-4 ms per call | ||
// Composed: 3e+6 calls, 1982 ms, 6.607e-4 ms per call | ||
// Overhead: 6.3%, 3.9e-5 ms per call | ||
console.time('optimal1'); | ||
for (i = 0; i < I_MAX; i++) { | ||
r = fOptimal1(1, d); | ||
r = fOptimal1('hi', false); | ||
r = fOptimal1(2, 4); | ||
} | ||
console.timeEnd('optimal1'); | ||
console.time('optimal2'); | ||
for (i = 0; i < I_MAX; i++) { | ||
r = fOptimal2(1, d); | ||
r = fOptimal2('hi', false); | ||
r = fOptimal2(2, 4); | ||
} | ||
console.timeEnd('optimal2'); | ||
console.time('optimal3'); | ||
for (i = 0; i < I_MAX; i++) { | ||
r = fOptimal3(1, d); | ||
r = fOptimal3('hi', false); | ||
r = fOptimal3(2, 4); | ||
} | ||
console.timeEnd('optimal3'); | ||
console.time('switch'); | ||
for (i = 0; i < I_MAX; i++) { | ||
r = fn1(1, d); | ||
r = fn1('hi', false); | ||
r = fn1(2, 4); | ||
} | ||
console.timeEnd('switch'); |
@@ -1,2 +0,2 @@ | ||
var compose = require('../index'); | ||
var compose = require('../function-composer'); | ||
@@ -3,0 +3,0 @@ // compose a new function |
# History | ||
## 2014-10-23, version 0.2.0 | ||
- Implemented support for named functions. | ||
- Implemented support for type conversions. | ||
- Implemented support for custom types. | ||
- Library packaged as UMD, usable with CommonJS (node.js), AMD, and browser globals. | ||
## 2014-10-21, version 0.1.0 | ||
@@ -5,0 +13,0 @@ |
{ | ||
"name": "function-composer", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "Compose functions with multiple type signatures", | ||
@@ -14,4 +14,5 @@ "author": "Jos de Jong <wjosdejong@gmail.com> (https://github.com/josdejong)", | ||
"function", | ||
"arguments", | ||
"compose", | ||
"signature" | ||
"types" | ||
], | ||
@@ -21,6 +22,8 @@ "dependencies": {}, | ||
"istanbul": "^0.3.2", | ||
"mocha": "^1.21.5" | ||
"mocha": "^1.21.5", | ||
"uglify-js": "^2.4.15" | ||
}, | ||
"main": "./index", | ||
"main": "./function-composer", | ||
"scripts": { | ||
"minify": "uglifyjs function-composer.js -o function-composer.min.js", | ||
"test": "mocha test --recursive", | ||
@@ -27,0 +30,0 @@ "coverage": "istanbul cover _mocha -- test --recursive; echo \"\nCoverage report is available at ./coverage/lcov-report/index.html\"" |
@@ -6,2 +6,8 @@ function-composer | ||
Features: | ||
- Compose multiple functions with different signatures into one. | ||
- Type-checking of input arguments. | ||
- Automatic type conversion of arguments. | ||
Supported environments: node.js, Chrome, Firefox, Safari, Opera, IE9+. | ||
@@ -50,12 +56,73 @@ | ||
# Performance | ||
Type checking input arguments adds some overhead to a function. For very small | ||
functions this overhead can be larger than the function execution itself is, | ||
but for any non-trivial function the overhead is typically small to neglectable. | ||
You need to keep in mind though that you probably would have to do the type | ||
checking done by `function-composer` anyway. | ||
# API | ||
## Construction | ||
```js | ||
compose(signatures: Object.<string, function>) : function | ||
compose(name: string, signatures: Object.<string, function>) : function | ||
``` | ||
## Properties | ||
- `compose.tests: Object` | ||
A map with type checking tests. Add custom types like: | ||
```js | ||
function Person(...) { | ||
... | ||
} | ||
compose.tests['Person'] = function (x) { | ||
return x instanceof Person; | ||
}; | ||
``` | ||
- `compose.conversions: Array` | ||
An Array with built-in conversions. Empty by default. Can be used for example | ||
to defined conversions from `boolean` to `number`. For example: | ||
```js | ||
compose.conversions.push({ | ||
from: 'boolean', | ||
to: 'number', | ||
convert: function (x) { | ||
return +x; | ||
}); | ||
``` | ||
# Roadmap | ||
- Create named functions. | ||
- Add new types (already possible but not yet documented) | ||
- Automatic casting, for example from boolean to number. | ||
## Version 1 | ||
- Extend function signatures: | ||
- Any type arguments like `*, boolean`. | ||
- Ellipsis like `string, ...`. | ||
- Optional arguments like `number?, array`. | ||
- Any type arguments like `'*, boolean'` | ||
- Ellipsis like `'string, ...'` | ||
- Optional arguments like `'number?, array'` | ||
- Multiple types per argument like `number | string, number'` | ||
- Create a good benchmark, to get insight in the overhead. | ||
- Add a bundle for use in the browser. | ||
## Version 2 | ||
- Extend function signatures: | ||
- Constants like `'"linear" | "cubic"'`. | ||
- Object definitions like `'{name: string, age: number}'` | ||
- Object definitions like `'Object.<string, Person>'` | ||
- Array definitions like `'Array.<Person>'` | ||
# Minify | ||
To generate the minified version of the library, run: | ||
npm run minify |
// test parse | ||
var assert = require('assert'); | ||
var compose = require('../index'); | ||
var compose = require('../function-composer'); | ||
describe('parse', function() { | ||
it('should compose an empty function', function() { | ||
var fn = compose({}); | ||
assert.throws(function () { | ||
fn(); | ||
}, /TypeError: Wrong function signature/); | ||
assert(fn.signatures instanceof Object); | ||
assert.deepEqual(fn.signatures, []); | ||
}); | ||
it('should compose a function with zero arguments', function() { | ||
@@ -21,2 +30,13 @@ var fns = { | ||
it('should create a named function', function() { | ||
var fn = compose('myFunction', { | ||
'': function () { | ||
return 'noargs'; | ||
} | ||
}); | ||
assert.equal(fn(), 'noargs'); | ||
assert.equal(fn.name, 'myFunction'); | ||
}); | ||
it('should compose a function with one argument', function() { | ||
@@ -37,3 +57,3 @@ var fns = { | ||
assert.equal(fn(2), 'number:2'); | ||
assert.equal(fn('hi'), 'string:hi'); | ||
assert.equal(fn('foo'), 'string:foo'); | ||
assert.equal(fn(false), 'boolean:false'); | ||
@@ -62,3 +82,3 @@ assert(fn.signatures instanceof Object); | ||
assert.equal(fn(2), 'number:2'); | ||
assert.equal(fn('hi'), 'string:hi'); | ||
assert.equal(fn('foo'), 'string:foo'); | ||
assert.equal(fn(2, false), 'number,boolean:2,false'); | ||
@@ -72,2 +92,19 @@ assert(fn.signatures instanceof Object); | ||
it('should correctly recognize Date from Object (both are an Object)', function() { | ||
var fns = { | ||
'Object': function (value) { | ||
assert(value instanceof Object); | ||
return 'Object'; | ||
}, | ||
'Date': function (value) { | ||
assert(value instanceof Date); | ||
return 'Date'; | ||
} | ||
}; | ||
var fn = compose(fns); | ||
assert.equal(fn({foo: 'bar'}), 'Object'); | ||
assert.equal(fn(new Date()), 'Date'); | ||
}); | ||
it('should throw an error when providing an unsupported type of argument', function() { | ||
@@ -90,7 +127,107 @@ var fn = compose({ | ||
assert.throws(function () {fn(1, 2)}, /TypeError: Wrong number of arguments/); // TODO: should be changed into an ArgumentsError? | ||
assert.throws(function () {fn(1, 2)}, /TypeError: Wrong number of arguments/); | ||
}); | ||
it('should throw an error when composing with an unknown type', function() { | ||
assert.throws(function () { | ||
var fn = compose({ | ||
'foo': function (value) { | ||
return 'number:' + value; | ||
} | ||
}); | ||
}, /Error: Unknown type "foo"/); | ||
}); | ||
// TODO: test compose.types | ||
it('should give a hint when composing with a wrongly cased type', function() { | ||
assert.throws(function () { | ||
var fn = compose({ | ||
'array': function (value) { | ||
return 'array:' + value; | ||
} | ||
}); | ||
}, /Error: Unknown type "array". Did you mean "Array"?/); | ||
assert.throws(function () { | ||
var fn = compose({ | ||
'Function': function (value) { | ||
return 'Function:' + value; | ||
} | ||
}); | ||
}, /Error: Unknown type "Function". Did you mean "function"?/); | ||
}); | ||
describe('conversions' , function () { | ||
before(function () { | ||
compose.conversions = [ | ||
{from: 'boolean', to: 'number', convert: function (x) {return +x;}}, | ||
{from: 'boolean', to: 'string', convert: function (x) {return x + '';}}, | ||
{from: 'number', to: 'string', convert: function (x) {return x + '';}} | ||
]; | ||
}); | ||
after(function () { | ||
compose.conversions = []; | ||
}); | ||
it('should add conversions to a function with one argument', function() { | ||
var fn = compose({ | ||
'string': function (a) { | ||
return a; | ||
} | ||
}); | ||
assert.equal(fn(2), '2'); | ||
assert.equal(fn(false), 'false'); | ||
assert.equal(fn('foo'), 'foo'); | ||
}); | ||
it('should add conversions to a function with multiple arguments', function() { | ||
// note: we add 'string, string' first, and `string, number` afterwards, | ||
// to test whether the conversions are correctly ordered. | ||
var fn = compose({ | ||
'string, string': function (a, b) { | ||
assert.equal(typeof a, 'string'); | ||
assert.equal(typeof b, 'string'); | ||
return 'string, string'; | ||
}, | ||
'string, number': function (a, b) { | ||
assert.equal(typeof a, 'string'); | ||
assert.equal(typeof b, 'number'); | ||
return 'string, number'; | ||
} | ||
}); | ||
assert.equal(fn(true, false), 'string, number'); | ||
assert.equal(fn(true, 2), 'string, number'); | ||
assert.equal(fn(true, 'foo'), 'string, string'); | ||
assert.equal(fn(2, false), 'string, number'); | ||
assert.equal(fn(2, 3), 'string, number'); | ||
assert.equal(fn(2, 'foo'), 'string, string'); | ||
assert.equal(fn('foo', true), 'string, number'); | ||
assert.equal(fn('foo', 2), 'string, number'); | ||
assert.equal(fn('foo', 'foo'), 'string, string'); | ||
}); | ||
it('should add non-conflicting conversions to a function with one argument', function() { | ||
var fn = compose({ | ||
'number': function (a) { | ||
return a; | ||
}, | ||
'string': function (a) { | ||
return a; | ||
} | ||
}); | ||
// booleans should be converted to number | ||
assert.equal(fn(false), 0); | ||
assert.equal(fn(true), 1); | ||
// numbers and strings should be left as is | ||
assert.equal(fn(2), 2); | ||
assert.equal(fn('foo'), 'foo'); | ||
}); | ||
}); | ||
// TODO: test compose.tests | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
32715
15
127
0
3
574