diff --git a/decorate-after/docs/types/index.d.ts b/decorate-after/docs/types/index.d.ts index e081e16e..e5430cd7 100644 --- a/decorate-after/docs/types/index.d.ts +++ b/decorate-after/docs/types/index.d.ts @@ -110,6 +110,7 @@ interface Decorator { * // returns 2 */ , U>( fcn: ( ...args: T ) => U, arity: number, after: ( v: U ) => void, thisArg?: any ): ( ...args: T ) => U; // tslint-disable-line max-line-length + /** * Uses code generation to decorate a provided function such that the function's return value is provided as an argument to another function. * diff --git a/decorate-after/lib/factory.js b/decorate-after/lib/factory.js index 7ae5d5b4..f634b9d7 100644 --- a/decorate-after/lib/factory.js +++ b/decorate-after/lib/factory.js @@ -207,6 +207,9 @@ function decorateAfter( fcn, arity, after, thisArg ) { // Close the outer function: f += '}'; + // Add a source directive for debugging: + f += '//# sourceURL=decorateAfter.factory.js'; + // Create the function the global scope: return (new Function( f ))()( fcn, after, thisArg ); } diff --git a/decorate-after/lib/main.js b/decorate-after/lib/main.js index 1b30e320..8b938a4a 100644 --- a/decorate-after/lib/main.js +++ b/decorate-after/lib/main.js @@ -163,7 +163,7 @@ function decorateAfter( fcn, arity, after, thisArg ) { var args; var i; - // NOTE: the use of a `for` loop is intentional (both here and below), as JavaScript does not require that only a fix number of arguments be provided; the number of provided arguments may be more or less than the signature specifies. + // NOTE: the use of a `for` loop is intentional (both here and below), as JavaScript does not require that only a fixed number of arguments be provided; the number of provided arguments may be more or less than the signature specifies. args = []; for ( i = 0; i < arguments.length; i++ ) { diff --git a/lib/index.js b/lib/index.js index d4a407e0..90def31d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1741,6 +1741,15 @@ setReadOnly( utils, 'tabulate', require( './../tabulate' ) ); */ setReadOnly( utils, 'tabulateBy', require( './../tabulate-by' ) ); +/** +* @name thunk +* @memberof utils +* @readonly +* @type {Function} +* @see {@link module:@stdlib/utils/thunk} +*/ +setReadOnly( utils, 'thunk', require( './../thunk' ) ); + /** * @name timeit * @memberof utils diff --git a/thunk/README.md b/thunk/README.md new file mode 100644 index 00000000..6e886620 --- /dev/null +++ b/thunk/README.md @@ -0,0 +1,134 @@ + + +# thunk + +> Generate a [thunk][thunk]. + + + +
+ +
+ + + + + +
+ +## Usage + +```javascript +var thunk = require( '@stdlib/utils/thunk' ); +``` + +#### thunk( fcn\[, ...args] ) + +Returns a [thunk][thunk] (i.e., an anonymous function having arity `0` and which invokes a provided function with specified arguments). + +```javascript +var add = require( '@stdlib/math/base/ops/add' ); + +var f = thunk( add, 2.0, 3.0 ); +// returns + +var v = f(); +// returns 5 + +// ... + +v = f(); +// returns 5 +``` + +
+ + + + + +
+ +
+ + + + + +
+ +## Examples + + + +```javascript +var add = require( '@stdlib/math/base/ops/add' ); +var decorateAfter = require( '@stdlib/utils/decorate-after' ); +var discreteUniform = require( '@stdlib/random/base/discrete-uniform' ).factory; +var thunk = require( '@stdlib/utils/thunk' ); + +function log( v ) { + console.log( v ); +} + +// Create a PRNG for generating uniformly distributed pseudorandom integers: +var randi = discreteUniform( 100, 1000 ); + +// Randomly delay evaluation of various thunks... +var i; +for ( i = 0; i < 10; i++ ) { + setTimeout( decorateAfter( thunk( add, i, i+1 ), 0, log ), randi() ); +} +``` + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + diff --git a/thunk/benchmark/benchmark.js b/thunk/benchmark/benchmark.js new file mode 100644 index 00000000..54a28e70 --- /dev/null +++ b/thunk/benchmark/benchmark.js @@ -0,0 +1,72 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2022 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var bench = require( '@stdlib/bench' ); +var isFunction = require( '@stdlib/assert/is-function' ); +var isnan = require( '@stdlib/math/base/assert/is-nan' ); +var abs = require( '@stdlib/math/base/special/abs' ); +var pkg = require( './../package.json' ).name; +var thunk = require( './../lib' ); + + +// MAIN // + +bench( pkg, function benchmark( b ) { + var out; + var i; + + b.tic(); + for ( i = 0; i < b.iterations; i++ ) { + out = thunk( abs, -0.0 ); + if ( typeof out !== 'function' ) { + b.fail( 'should return a function' ); + } + } + b.toc(); + if ( !isFunction( out ) ) { + b.fail( 'should return a function' ); + } + b.pass( 'benchmark finished' ); + b.end(); +}); + +bench( pkg+'::thunk', function benchmark( b ) { + var fcn; + var out; + var i; + + fcn = thunk( abs, -0.0 ); + + b.tic(); + for ( i = 0; i < b.iterations; i++ ) { + out = fcn(); // NOTE: this may get optimized away as returning the same value for each loop iteration + if ( out !== out ) { + b.fail( 'should not return NaN' ); + } + } + b.toc(); + if ( isnan( out ) ) { + b.fail( 'should not return NaN' ); + } + b.pass( 'benchmark finished' ); + b.end(); +}); diff --git a/thunk/docs/repl.txt b/thunk/docs/repl.txt new file mode 100644 index 00000000..f3190291 --- /dev/null +++ b/thunk/docs/repl.txt @@ -0,0 +1,28 @@ + +{{alias}}( fcn[, ...args] ) + Returns a thunk. + + Parameters + ---------- + fcn: Function + Function to convert to a thunk. + + args: ...any (optional) + Function arguments. + + Returns + ------- + out: Function + Thunk. + + Examples + -------- + > var fcn = {{alias}}( {{alias:@stdlib/math/base/ops/add}}, 2, 3 ); + > var v = fcn() + 5 + > v = fcn() + 5 + + See Also + -------- + diff --git a/thunk/docs/types/index.d.ts b/thunk/docs/types/index.d.ts new file mode 100644 index 00000000..cc40a901 --- /dev/null +++ b/thunk/docs/types/index.d.ts @@ -0,0 +1,45 @@ +/* +* @license Apache-2.0 +* +* Copyright (c) 2022 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +// TypeScript Version: 2.0 + +/** +* Returns a thunk. +* +* @param fcn - function to convert to a thunk +* @param args - function args +* @returns thunk +* +* @example +* var add = require( `@stdlib/math/base/ops/add` ); +* +* var f = thunk( add, 2, 3 ); +* // returns +* +* // ... +* +* // Evaluate the thunk: +* var v = f(); +* // returns 5 +*/ +declare function thunk, U>( fcn: ( ...args: T ) => U, ...args: T ): () => U; // tslint-disable-line max-line-length + + +// EXPORTS // + +export = thunk; diff --git a/thunk/docs/types/test.ts b/thunk/docs/types/test.ts new file mode 100644 index 00000000..357c6641 --- /dev/null +++ b/thunk/docs/types/test.ts @@ -0,0 +1,58 @@ +/* +* @license Apache-2.0 +* +* Copyright (c) 2022 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import thunk = require( './index' ); + + +// TESTS // + +// The function returns a function... +{ + thunk<[], number>( (): number => 2 ); // $ExpectType () => number + thunk, number>( ( x: number ): number => x, 2 ); // $ExpectType () => number + thunk<[number, number], number>( ( x: number, y: number ): number => x + y, 2, 3 ); // $ExpectType () => number +} + +// The compiler throws an error if the function is provided a first argument which is not a function... +{ + thunk( true ); // $ExpectError + thunk( false ); // $ExpectError + thunk( 5 ); // $ExpectError + thunk( [] ); // $ExpectError + thunk( {} ); // $ExpectError + thunk( 'abc' ); // $ExpectError + + thunk( true, 2 ); // $ExpectError + thunk( false, 2 ); // $ExpectError + thunk( 5, 2 ); // $ExpectError + thunk( [], 2 ); // $ExpectError + thunk( {}, 2 ); // $ExpectError + thunk( 'abc', 2 ); // $ExpectError + + thunk( true, 2, 3 ); // $ExpectError + thunk( false, 2, 3 ); // $ExpectError + thunk( 5, 2, 3 ); // $ExpectError + thunk( [], 2, 3 ); // $ExpectError + thunk( {}, 2, 3 ); // $ExpectError + thunk( 'abc', 2, 3 ); // $ExpectError +} + +// The compiler throws an error if the function is provided an incorrect number of arguments... +{ + thunk(); // $ExpectError +} diff --git a/thunk/examples/index.js b/thunk/examples/index.js new file mode 100644 index 00000000..c26b1833 --- /dev/null +++ b/thunk/examples/index.js @@ -0,0 +1,37 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2022 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +var add = require( '@stdlib/math/base/ops/add' ); +var decorateAfter = require( './../../decorate-after' ); +var discreteUniform = require( '@stdlib/random/base/discrete-uniform' ).factory; +var thunk = require( './../lib' ); + +function log( v ) { + console.log( v ); +} + +// Create a PRNG for generating uniformly distributed pseudorandom integers: +var randi = discreteUniform( 100, 1000 ); + +// Randomly delay evaluation of various thunks... +var i; +for ( i = 0; i < 10; i++ ) { + setTimeout( decorateAfter( thunk( add, i, i+1 ), 0, log ), randi() ); +} diff --git a/thunk/lib/index.js b/thunk/lib/index.js new file mode 100644 index 00000000..b8664437 --- /dev/null +++ b/thunk/lib/index.js @@ -0,0 +1,47 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2022 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +/** +* Generate a thunk. +* +* @module @stdlib/utils/thunk +* +* @example +* var add = require( '@stdlib/math/base/ops/add' ); +* var thunk = require( '@stdlib/utils/thunk' ); +* +* var f = thunk( add, 2, 3 ); +* // returns +* +* // ... +* +* // Evaluate the thunk: +* var v = f(); +* // returns 5 +*/ + +// MODULES // + +var main = require( './main.js' ); + + +// EXPORTS // + +module.exports = main; diff --git a/thunk/lib/main.js b/thunk/lib/main.js new file mode 100644 index 00000000..32143b30 --- /dev/null +++ b/thunk/lib/main.js @@ -0,0 +1,68 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2022 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var isFunction = require( '@stdlib/assert/is-function' ); +var format = require( '@stdlib/string/format' ); + + +// MAIN // + +/** +* Returns a thunk. +* +* @param {Function} fcn - function to convert to a thunk +* @param {...*} [args] - function arguments +* @throws {TypeError} first argument must be a function +* @returns {Function} thunk +* +* @example +* var add = require( '@stdlib/math/base/ops/add' ); +* +* var f = thunk( add, 2, 3 ); +* // returns +* +* // ... +* +* // Evaluate the thunk: +* var v = f(); +* // returns 5 +*/ +function thunk( fcn ) { + var args; + var i; + if ( !isFunction( fcn ) ) { + throw new TypeError( format( 'invalid argument. First argument must be a function. Value: `%s`.', fcn ) ); + } + args = []; + for ( i = 1; i < arguments.length; i++ ) { + args.push( arguments[ i ] ); + } + // NOTE: we return an anonymous function to satisfy the definition of a thunk (i.e., an anonymous function which has no parameters of its own and wraps another function)... + return function () { // eslint-disable-line no-restricted-syntax, func-names + return fcn.apply( null, args ); + }; +} + + +// EXPORTS // + +module.exports = thunk; diff --git a/thunk/package.json b/thunk/package.json new file mode 100644 index 00000000..3ca198fe --- /dev/null +++ b/thunk/package.json @@ -0,0 +1,68 @@ +{ + "name": "@stdlib/utils/thunk", + "version": "0.0.0", + "description": "Generate a thunk.", + "license": "Apache-2.0", + "author": { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/stdlib/graphs/contributors" + }, + "contributors": [ + { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/stdlib/graphs/contributors" + } + ], + "main": "./lib", + "directories": { + "benchmark": "./benchmark", + "doc": "./docs", + "example": "./examples", + "lib": "./lib", + "test": "./test" + }, + "types": "./docs/types", + "scripts": {}, + "homepage": "https://github.com/stdlib-js/stdlib", + "repository": { + "type": "git", + "url": "git://github.com/stdlib-js/stdlib.git" + }, + "bugs": { + "url": "https://github.com/stdlib-js/stdlib/issues" + }, + "dependencies": {}, + "devDependencies": {}, + "engines": { + "node": ">=0.10.0", + "npm": ">2.7.0" + }, + "os": [ + "aix", + "darwin", + "freebsd", + "linux", + "macos", + "openbsd", + "sunos", + "win32", + "windows" + ], + "keywords": [ + "stdlib", + "stdutils", + "stdutil", + "utilities", + "utils", + "util", + "thunk", + "delay", + "wrap", + "lazy", + "function", + "fun", + "func", + "fcn", + "functional" + ] +} diff --git a/thunk/test/test.js b/thunk/test/test.js new file mode 100644 index 00000000..e3cd75ea --- /dev/null +++ b/thunk/test/test.js @@ -0,0 +1,163 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2022 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var tape = require( 'tape' ); +var functionName = require( './../../function-name' ); +var add = require( '@stdlib/math/base/ops/add' ); +var thunk = require( './../lib' ); + + +// TESTS // + +tape( 'main export is a function', function test( t ) { + t.ok( true, __filename ); + t.equal( typeof thunk, 'function', 'main export is a function' ); + t.end(); +}); + +tape( 'the function throws an error if provided a first argument which is not a function', function test( t ) { + var values; + var i; + + values = [ + '5', + 5, + NaN, + null, + void 0, + true, + false, + [], + {} + ]; + + for ( i = 0; i < values.length; i++ ) { + t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] ); + } + t.end(); + + function badValue( value ) { + return function badValue() { + thunk( value ); + }; + } +}); + +tape( 'the function throws an error if provided a first argument which is not a function (2 args)', function test( t ) { + var values; + var i; + + values = [ + '5', + 5, + NaN, + null, + void 0, + true, + false, + [], + {} + ]; + + for ( i = 0; i < values.length; i++ ) { + t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] ); + } + t.end(); + + function badValue( value ) { + return function badValue() { + thunk( value, 2 ); + }; + } +}); + +tape( 'the function throws an error if provided a first argument which is not a function (3 args)', function test( t ) { + var values; + var i; + + values = [ + '5', + 5, + NaN, + null, + void 0, + true, + false, + [], + {} + ]; + + for ( i = 0; i < values.length; i++ ) { + t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] ); + } + t.end(); + + function badValue( value ) { + return function badValue() { + thunk( value, 2, 3 ); + }; + } +}); + +tape( 'the function returns an anonymous function having arity 0', function test( t ) { + var fcn = thunk( add, 2, 3 ); + t.strictEqual( typeof fcn, 'function', 'returns expected value' ); + t.strictEqual( functionName( fcn ), '', 'returns expected value' ); + t.strictEqual( fcn.length, 0, 'returns expected value' ); + t.end(); +}); + +tape( 'the function returns a thunk (nullary)', function test( t ) { + var fcn = thunk( constant ); + t.strictEqual( fcn(), 3.14, 'returns expected value' ); + t.strictEqual( fcn(), 3.14, 'returns expected value' ); + t.strictEqual( fcn(), 3.14, 'returns expected value' ); + t.end(); + + function constant() { + return 3.14; + } +}); + +tape( 'the function returns a thunk (unary)', function test( t ) { + var fcn = thunk( scale, 3.0 ); + t.strictEqual( fcn(), 4.0, 'returns expected value' ); + t.strictEqual( fcn(), 4.0, 'returns expected value' ); + t.strictEqual( fcn(), 4.0, 'returns expected value' ); + t.end(); + + function scale( v ) { + return v + 1.0; + } +}); + +tape( 'the function returns a thunk (binary)', function test( t ) { + var fcn = thunk( add, 3.0, 1.0 ); + t.strictEqual( fcn(), 4.0, 'returns expected value' ); + t.strictEqual( fcn(), 4.0, 'returns expected value' ); + t.strictEqual( fcn(), 4.0, 'returns expected value' ); + t.end(); + + function add( x, y ) { + return x + y; + } +});