Curried utility functions and functional programming helpers for composition and partial application.
Core utilities for transforming and composing functions.
/**
* Transform function to accept arguments one at a time (curry)
* @param {Function} fn - Function to curry
* @param {...*} args - Optional arguments to pre-apply
* @returns {Function} Curried function
*/
function _.curry(fn, ...args);
/**
* Curry function with specific arity
* @param {number} n - Number of arguments before execution
* @param {Function} fn - Function to curry
* @param {...*} args - Optional arguments to pre-apply
* @returns {Function} Curried function
*/
function _.ncurry(n, fn, ...args);
/**
* Partially apply arguments to function
* @param {Function} fn - Function to partially apply
* @param {...*} args - Arguments to pre-apply
* @returns {Function} Partially applied function
*/
function _.partial(fn, ...args);
/**
* Swap the order of first two arguments
* @param {Function} fn - Function to flip
* @param {*} x - First argument
* @param {*} y - Second argument
* @returns {*} Result of fn(y, x)
*/
function _.flip(fn, x, y);Usage Examples:
// Basic currying
const add = _.curry((a, b, c) => a + b + c);
console.log(add(1)(2)(3)); // 6
console.log(add(1, 2)(3)); // 6
console.log(add(1)(2, 3)); // 6
// Currying with pre-applied arguments
const addFive = _.curry((a, b) => a + b, 5);
console.log(addFive(3)); // 8
// N-curry for functions without explicit arity
const sum = _.ncurry(3, (...args) => args.reduce((a, b) => a + b, 0));
console.log(sum(1)(2)(3)); // 6
// Partial application
const multiply = (a, b, c) => a * b * c;
const double = _.partial(multiply, 2);
console.log(double(3, 4)); // 24
// Flip argument order
const divide = (a, b) => a / b;
console.log(divide(10, 2)); // 5
console.log(_.flip(divide, 10, 2)); // 0.2Combine multiple functions into single operations.
/**
* Compose functions right-to-left (mathematical composition)
* @param {...Function} functions - Functions to compose
* @returns {Function} Composed function
*/
function _.compose(...functions);
/**
* Compose functions left-to-right (pipeline style)
* @param {...Function} functions - Functions to compose
* @returns {Function} Composed function
*/
function _.seq(...functions);Usage Examples:
// Right-to-left composition
const add1 = x => x + 1;
const multiply2 = x => x * 2;
const square = x => x * x;
const transform = _.compose(square, multiply2, add1);
console.log(transform(3)); // square(multiply2(add1(3))) = square(8) = 64
// Left-to-right composition (more intuitive)
const pipeline = _.seq(add1, multiply2, square);
console.log(pipeline(3)); // square(multiply2(add1(3))) = square(8) = 64
// String processing pipeline
const processText = _.seq(
str => str.toLowerCase(),
str => str.trim(),
str => str.replace(/\s+/g, '-')
);
console.log(processText(' Hello World ')); // "hello-world"Highland provides only a few top-level method aliases, not full curried versions of all stream methods.
// Available top-level method aliases (only these two)
const _.tap = _.doto; // Alias for the doto method
const _.series = _.sequence; // Alias for the sequence methodImportant Note: Unlike some other functional libraries, Highland does not provide curried versions of all stream methods at the top level. Most stream methods must be called on stream instances using the
.method()Usage Examples:
// Since Highland doesn't provide curried versions, use stream chaining
const processNumbers = stream => stream
.map(x => parseInt(x))
.filter(x => !isNaN(x))
.map(x => x * 2)
.filter(x => x > 10);
_(['5', '10', 'invalid', '8', '12'])
.through(processNumbers)
.toArray(console.log); // [20, 24]
// Alternative using composition with function wrapping
const processData = _.seq(
data => _(data).map(x => parseInt(x)),
stream => stream.filter(x => !isNaN(x)),
stream => stream.map(x => x * 2),
stream => stream.filter(x => x > 10)
);
// Using available aliases
_([1, 2, 3])
.doto(console.log) // Same as .tap(console.log) since _.tap = _.doto
.toArray(console.log);Basic mathematical operations as curried functions.
/**
* Add two numbers
* @param {number} a - First number
* @param {number} b - Second number
* @returns {number} Sum of a and b
*/
const _.add = _.curry((a, b) => a + b);
/**
* Logical negation
* @param {*} x - Value to negate
* @returns {boolean} !x
*/
function _.not(x);Usage Examples:
// Mathematical operations
const add10 = _.add(10);
console.log(add10(5)); // 15
const numbers = [1, 2, 3, 4, 5];
_(numbers).map(_.add(10)).toArray(console.log); // [11, 12, 13, 14, 15]
// Logical operations
const isEven = x => x % 2 === 0;
const isOdd = _.compose(_.not, isEven);
_([1, 2, 3, 4]).filter(isOdd).toArray(console.log); // [1, 3]Functional utilities for working with objects.
/**
* Extract property from object
* @param {string} prop - Property name
* @param {Object} obj - Object to extract from
* @returns {*} Property value
*/
const _.get = _.curry((prop, obj) => obj[prop]);
/**
* Set property on object (mutating)
* @param {string} prop - Property name
* @param {*} value - Value to set
* @param {Object} obj - Object to modify
* @returns {Object} Modified object
*/
const _.set = _.curry((prop, value, obj) => { obj[prop] = value; return obj; });
/**
* Extend object with properties (mutating)
* @param {Object} extensions - Properties to add
* @param {Object} target - Target object
* @returns {Object} Extended object
*/
const _.extend = _.curry((extensions, target) => Object.assign(target, extensions));
/**
* Get object keys as stream
* @param {Object} obj - Object to get keys from
* @returns {Stream} Stream of keys
*/
function _.keys(obj);
/**
* Get object values as stream
* @param {Object} obj - Object to get values from
* @returns {Stream} Stream of values
*/
function _.values(obj);
/**
* Get object key-value pairs as stream
* @param {Object} obj - Object to get pairs from
* @returns {Stream} Stream of [key, value] arrays
*/
function _.pairs(obj);Usage Examples:
// Property extraction
const getName = _.get('name');
const users = [{name: 'Alice'}, {name: 'Bob'}];
_(users).map(getName).toArray(console.log); // ['Alice', 'Bob']
// Object extension for default values
const withDefaults = _.extend({active: true, role: 'user'});
const user = withDefaults({name: 'Alice'});
console.log(user); // {name: 'Alice', active: true, role: 'user'}
// Object streams
const obj = {a: 1, b: 2, c: 3};
_.keys(obj).toArray(console.log); // ['a', 'b', 'c']
_.values(obj).toArray(console.log); // [1, 2, 3]
_.pairs(obj).toArray(console.log); // [['a', 1], ['b', 2], ['c', 3]]Utilities for working with Node.js callbacks and async operations.
/**
* Wrap Node.js callback function to return Highland stream
* @param {Function} fn - Node-style callback function
* @param {Array|Function|number} mappingHint - How to handle multiple callback args
* @returns {Function} Function returning Highland stream
*/
function _.wrapCallback(fn, mappingHint);
/**
* Add stream-returning versions of all functions on object/constructor
* @param {Object|Function} source - Object or constructor to streamify
* @returns {Object|Function} Source with added stream methods
*/
function _.streamifyAll(source);Usage Examples:
const fs = require('fs');
// Wrap callback functions
const readFile = _.wrapCallback(fs.readFile);
const stat = _.wrapCallback(fs.stat);
// Use wrapped functions in streams
_(['file1.txt', 'file2.txt'])
.flatMap(filename => readFile(filename, 'utf8'))
.each(content => console.log('File content:', content));
// Streamify entire modules
const streamFs = _.streamifyAll(fs);
streamFs.readFileStream('package.json', 'utf8')
.each(content => console.log('Package:', JSON.parse(content).name));
// Custom mapping for multiple callback arguments
const execCallback = _.wrapCallback(require('child_process').exec,
(stdout, stderr) => ({stdout, stderr}));
execCallback('ls -la')
.each(result => {
console.log('Output:', result.stdout);
if (result.stderr) console.error('Error:', result.stderr);
});Simple utilities for debugging functional pipelines.
/**
* Console.log wrapper for functional use
* @param {...*} args - Arguments to log
* @returns {void}
*/
function _.log(...args);Usage Examples:
// Debug stream processing
_([1, 2, 3, 4])
.map(x => x * 2)
.tap(_.log) // Logs each doubled value
.filter(x => x > 4)
.each(_.log); // Logs final filtered values
// Use in function composition
const debugPipeline = _.seq(
_.map(x => x + 1),
_.tap(_.log), // Debug intermediate values
_.filter(x => x % 2 === 0),
_.each(_.log) // Debug final values
);
_([1, 2, 3, 4]).through(debugPipeline);Highland supports point-free programming through function composition, though without curried stream methods:
// Traditional style
const processUsers = users => _(users)
.filter(user => user.active)
.map(user => user.name)
.map(name => name.toUpperCase());
// Point-free style using _.seq with Highland's actual API
const processUsers = _.seq(
users => _(users).filter(user => user.active),
stream => stream.pluck('name'),
stream => stream.map(name => name.toUpperCase())
);// Compose complex transformations using Highland's actual API
const analyzeText = stream => stream
.flatMap(line => _(line.split(' '))) // Split into words
.map(word => word.toLowerCase())
.filter(word => word.length > 3)
.group() // Group identical words
.map(([word, occurrences]) => ({word, count: occurrences.length}))
.sortBy((a, b) => b.count - a.count);
_(['Hello world', 'Hello again world'])
.through(analyzeText)
.each(_.log);Highland supports the transducer protocol for efficient transformations:
/**
* Apply transducer to stream
* @param {Function} transducer - Transducer function
* @returns {Stream} Transformed stream
*/
Stream.prototype.transduce(transducer);Usage Examples:
// Using transducers (requires transducers-js or similar)
const xf = require('transducers-js');
const transform = xf.comp(
xf.map(x => x + 1),
xf.filter(x => x % 2 === 0)
);
_([1, 2, 3, 4, 5])
.transduce(transform)
.toArray(console.log); // [2, 4, 6]