Lodash provides rich functional programming tools. Here is a detailed answer about Lodash functional programming:
Lodash Functional Programming Overview
Lodash provides many functional programming tools, including function composition, currying, memoization, and more. These tools help developers write more concise and maintainable code.
1. Function Composition
_.flow([funcs])
Creates a function that executes the given functions from left to right.
javascriptfunction square(n) { return n * n; } function addTwo(n) { return n + 2; } var addSquare = _.flow(addTwo, square); addSquare(3); // => 25 // Real application: Data transformation pipeline function processData(data) { return _.flow( filterActive, transformData, sortByDate )(data); } function filterActive(items) { return _.filter(items, item => item.active); } function transformData(items) { return _.map(items, item => ({ id: item.id, name: _.upperFirst(item.name), value: _.round(item.value, 2) })); } function sortByDate(items) { return _.orderBy(items, ['createdAt'], ['desc']); }
_.flowRight([funcs])
Creates a function that executes the given functions from right to left (similar to function composition).
javascriptfunction square(n) { return n * n; } function addTwo(n) { return n + 2; } var addSquare = _.flowRight(square, addTwo); addSquare(3); // => 25 // Real application: Validation and processing function validateAndProcess(input) { return _.flowRight( processResult, validateInput, sanitizeInput )(input); } function sanitizeInput(input) { return _.trim(input); } function validateInput(input) { if (!input) throw new Error('Invalid input'); return input; } function processResult(input) { return _.upperFirst(input); }
2. Function Currying
_.curry(func, [arity=func.length])
Creates a function that accepts one or more arguments. If the number of arguments provided is less than arity, it returns a function that accepts the remaining arguments.
javascriptvar abc = function(a, b, c) { return [a, b, c]; }; var curried = _.curry(abc); curried(1)(2)(3); // => [1, 2, 3] curried(1, 2)(3); // => [1, 2, 3] curried(1, 2, 3); // => [1, 2, 3] // Real application: Create reusable functions const multiply = (a, b) => a * b; const double = _.curry(multiply)(2); const triple = _.curry(multiply)(3); console.log(double(5)); // => 10 console.log(triple(5)); // => 15 // Real application: API requests const fetchData = (baseUrl, endpoint, params) => { return fetch(`${baseUrl}/${endpoint}?${new URLSearchParams(params)}`); }; const fetchFromAPI = _.curry(fetchData)('https://api.example.com'); const fetchUsers = fetchFromAPI('users'); const fetchPosts = fetchFromAPI('posts'); fetchUsers({ page: 1, limit: 10 }); fetchPosts({ userId: 123 });
_.curryRight(func, [arity=func.length])
Similar to _.curry, but arguments are applied from right to left.
javascriptvar abc = function(a, b, c) { return [a, b, c]; }; var curried = _.curryRight(abc); curried(3)(2)(1); // => [1, 2, 3] curried(3, 2)(1); // => [1, 2, 3] curried(3, 2, 1); // => [1, 2, 3]
_.partial(func, [partials])
Creates a function that invokes func with partial arguments prepended.
javascriptvar greet = function(greeting, punctuation, name) { return greeting + ' ' + name + punctuation; }; var sayHelloTo = _.partial(greet, 'hello', '!'); var sayHelloToFred = sayHelloTo('fred'); sayHelloToFred; // => 'hello fred!' // Real application: Create functions with specific configuration const fetchWithConfig = _.partial(fetch, { headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer token' } }); fetchWithConfig('https://api.example.com/data');
_.partialRight(func, [partials])
Similar to _.partial, but partial arguments are appended.
javascriptvar greet = function(greeting, name, punctuation) { return greeting + ' ' + name + punctuation; }; var greetFred = _.partialRight(greet, 'fred'); var sayHelloToFred = _.partialRight(greetFred, '!'); sayHelloToFred('hello'); // => 'hello fred!'
3. Function Memoization
_.memoize(func, [resolver])
Creates a memoized function that caches function results.
javascriptvar object = { 'a': 1, 'b': 2 }; var other = { 'c': 3, 'd': 4 }; var values = _.memoize(_.values); values(object); // => [1, 2] values(other); // => [3, 4] object.a = 2; values(object); // => [1, 2] // cached result // Real application: Cache calculation results function fibonacci(n) { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2); } const memoizedFibonacci = _.memoize(fibonacci); console.time('fibonacci'); console.log(fibonacci(40)); // slow console.timeEnd('fibonacci'); console.time('memoized'); console.log(memoizedFibonacci(40)); // fast console.timeEnd('memoized'); // Real application: Cache API responses const fetchUser = _.memoize(async (userId) => { const response = await fetch(`/api/users/${userId}`); return response.json(); }, userId => userId); // First call sends request const user1 = await fetchUser(1); // Second call returns cache const user2 = await fetchUser(1);
4. Function Wrapping
_.wrap(value, [wrapper=identity])
Creates a function that provides value to the wrapper as its first argument.
javascriptvar p = _.wrap(_.escape, function(func, text) { return '<p>' + func(text) + '</p>'; }); p('fred, barney, & pebbles'); // => '<p>fred, barney, & pebbles</p>' // Real application: Wrap logging function const logWithTimestamp = _.wrap(console.log, (fn, ...args) => { const timestamp = new Date().toISOString(); fn(`[${timestamp}]`, ...args); }); logWithTimestamp('User logged in', { userId: 123 }); // => [2024-01-01T00:00:00.000Z] User logged in { userId: 123 }
_.negate(predicate)
Creates a function that negates the result of predicate.
javascriptfunction isEven(n) { return n % 2 === 0; } _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven)); // => [1, 3, 5] // Real application: Filter conditions const isActive = user => user.active; const isInactive = _.negate(isActive); const activeUsers = _.filter(users, isActive); const inactiveUsers = _.filter(users, isInactive);
5. Function Utilities
_.ary(func, [n=func.length])
Creates a function that invokes func with at most n arguments.
javascript_.map(['6', '8', '10'], _.ary(parseInt, 1)); // => [6, 8, 10] // Real application: Limit argument count const logFirstTwo = _.ary(console.log, 2); logFirstTwo('a', 'b', 'c'); // only outputs 'a', 'b'
_.unary(func)
Creates a function that accepts only one argument.
javascript_.map(['6', '8', '10'], _.unary(parseInt)); // => [6, 8, 10] // Real application: Ensure function accepts only one argument const logFirst = _.unary(console.log); logFirst('a', 'b', 'c'); // only outputs 'a'
_.once(func)
Creates a function that is restricted to invoking func once.
javascriptvar initialize = _.once(createApplication); initialize(); initialize(); // `createApplication` is invoked only once // Real application: Singleton initialization let database = null; const getDatabase = _.once(() => { database = connectToDatabase(); return database; }); const db1 = getDatabase(); const db2 = getDatabase(); console.log(db1 === db2); // true
_.before(n, func)
Creates a function that invokes func at most n times.
javascript_.before(2, function() { console.log('called'); })(); // => logs 'called' _.before(2, function() { console.log('called'); })(); // => logs 'called' _.before(2, function() { console.log('called'); })(); // => does not log // Real application: Limit call count const logFirstThree = _.before(3, console.log); logFirstThree('1'); // logs logFirstThree('2'); // logs logFirstThree('3'); // logs logFirstThree('4'); // does not log
_.after(n, func)
Creates a function that invokes func after it has been called n times.
javascriptvar saves = ['profile', 'settings']; var done = _.after(saves.length, function() { console.log('done saving!'); }); _.forEach(saves, function(type) { asyncSave({ type: type, complete: done }); }); // => logs 'done saving!' after the two async saves have completed // Real application: Wait for multiple async operations to complete const waitForAll = (count, callback) => { let remaining = count; return _.after(count, () => callback()); }; const done = waitForAll(3, () => { console.log('All tasks completed'); }); // Simulate async tasks setTimeout(() => done(), 100); setTimeout(() => done(), 200); setTimeout(() => done(), 300);
_.spread(func, [start=0])
Creates a function that invokes func with an array of arguments.
javascriptvar say = _.spread(function(who, what) { return who + ' says ' + what; }); say(['fred', 'hello']); // => 'fred says hello' // Real application: Handle array arguments const max = _.spread(Math.max); console.log(max([1, 2, 3, 4, 5])); // => 5 const sum = _.spread((...args) => _.sum(args)); console.log(sum([1, 2, 3, 4, 5])); // => 15
Real-World Application Examples
Functional Data Processing Pipeline
javascriptclass DataPipeline { constructor() { this.pipeline = []; } pipe(func) { this.pipeline.push(func); return this; } process(data) { return _.flow(...this.pipeline)(data); } } const pipeline = new DataPipeline() .pipe(data => _.filter(data, item => item.active)) .pipe(data => _.map(data, item => ({ id: item.id, name: _.upperFirst(item.name), value: _.round(item.value, 2) }))) .pipe(data => _.orderBy(data, ['createdAt'], ['desc'])) .pipe(data => _.take(data, 10)); const result = pipeline.process(rawData);
Functional API Client
javascriptclass APIClient { constructor(baseUrl) { this.baseUrl = baseUrl; } request = _.curry((method, endpoint, params) => { const url = `${this.baseUrl}${endpoint}`; const options = { method, headers: { 'Content-Type': 'application/json' } }; if (method === 'GET') { url += '?' + new URLSearchParams(params); } else { options.body = JSON.stringify(params); } return fetch(url, options).then(res => res.json()); }); get = this.request('GET'); post = this.request('POST'); put = this.request('PUT'); delete = this.request('DELETE'); getUsers = this.get('/users'); getUser = this.get('/users/:id'); createUser = this.post('/users'); updateUser = this.put('/users/:id'); deleteUser = this.delete('/users/:id'); } const api = new APIClient('https://api.example.com'); api.getUsers({ page: 1, limit: 10 }); api.getUser({ id: 123 }); api.createUser({ name: 'John', email: 'john@example.com' });
Functional Validator
javascriptclass Validator { constructor() { this.validators = []; } add(validator) { this.validators.push(validator); return this; } validate(data) { const results = this.validators.map(validator => validator(data)); const errors = _.filter(results, result => !result.valid); return { valid: errors.length === 0, errors: _.flatMap(errors, error => error.errors) }; } static required(field) { return data => ({ valid: !_.isEmpty(data[field]), errors: _.isEmpty(data[field]) ? [`${field} is required`] : [] }); } static minLength(field, length) { return data => ({ valid: data[field] && data[field].length >= length, errors: !data[field] || data[field].length < length ? [`${field} must be at least ${length} characters`] : [] }); } static email(field) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return data => ({ valid: emailRegex.test(data[field]), errors: !emailRegex.test(data[field]) ? [`${field} must be a valid email`] : [] }); } } const userValidator = new Validator() .add(Validator.required('name')) .add(Validator.minLength('name', 2)) .add(Validator.required('email')) .add(Validator.email('email')); const result = userValidator.validate({ name: 'John', email: 'john@example.com' });
Summary
Lodash provides rich functional programming tools, including:
- Function Composition:
_.flow,_.flowRight- Combine multiple functions into one - Function Currying:
_.curry,_.curryRight,_.partial,_.partialRight- Pre-fill partial arguments - Function Memoization:
_.memoize- Cache function results to improve performance - Function Wrapping:
_.wrap,_.negate- Wrap or modify function behavior - Function Utilities:
_.ary,_.unary,_.once,_.before,_.after,_.spread- Control function invocation
Mastering these functional programming tools can help developers write more concise, maintainable, and efficient code. In actual development, it's recommended to choose appropriate tools based on specific needs and make full use of the advantages of functional programming.