0
# Function Functions
1
2
Ramda provides 55 powerful functions for function composition, currying, and higher-order operations. These functions enable the creation of elegant, reusable code through functional programming patterns.
3
4
## Function Composition
5
6
### compose
7
Right-to-left function composition (mathematical style).
8
9
```javascript { .api }
10
/**
11
* @param {...Function} fns - Functions to compose (right to left)
12
* @returns {Function} Composed function
13
*/
14
R.compose(...fns)
15
16
// Mathematical composition: (f ∘ g)(x) = f(g(x))
17
const transform = R.compose(
18
R.join('-'), // 4. Join with dashes
19
R.map(R.toLower), // 3. Convert to lowercase
20
R.split(' '), // 2. Split by spaces
21
R.trim // 1. Remove whitespace (executed first)
22
);
23
24
transform(' Hello World '); // => 'hello-world'
25
26
// Step by step execution:
27
// 1. R.trim(' Hello World ') => 'Hello World'
28
// 2. R.split(' ', 'Hello World') => ['Hello', 'World']
29
// 3. R.map(R.toLower, ['Hello', 'World']) => ['hello', 'world']
30
// 4. R.join('-', ['hello', 'world']) => 'hello-world'
31
32
// Mathematical operations
33
const calculate = R.compose(
34
Math.abs, // 3. Get absolute value
35
R.subtract(10), // 2. Subtract 10
36
R.multiply(2) // 1. Multiply by 2 (executed first)
37
);
38
39
calculate(3); // => 4
40
// Steps: 3 * 2 = 6, 6 - 10 = -4, abs(-4) = 4
41
```
42
43
### pipe
44
Left-to-right function composition (natural reading order).
45
46
```javascript { .api }
47
/**
48
* @param {...Function} fns - Functions to compose (left to right)
49
* @returns {Function} Composed function
50
*/
51
R.pipe(...fns)
52
53
// Natural reading order: data flows left to right
54
const processUser = R.pipe(
55
R.prop('name'), // 1. Get name property
56
R.trim, // 2. Remove whitespace
57
R.toLower, // 3. Convert to lowercase
58
R.split(' '), // 4. Split into words
59
R.map(R.capitalize), // 5. Capitalize each word
60
R.join(' ') // 6. Join back together
61
);
62
63
const user = { name: ' john doe ' };
64
processUser(user); // => 'John Doe'
65
66
// Data processing pipeline
67
const analyzeNumbers = R.pipe(
68
R.filter(R.is(Number)), // 1. Keep only numbers
69
R.map(Math.abs), // 2. Get absolute values
70
R.sort(R.subtract), // 3. Sort ascending
71
R.takeLast(3), // 4. Take top 3
72
R.reduce(R.add, 0) // 5. Sum them
73
);
74
75
analyzeNumbers([1, -5, 'x', 3, -8, 2]); // => 10
76
// Steps: [1, -5, 3, -8, 2] → [1, 5, 3, 8, 2] → [1, 2, 3, 5, 8] → [3, 5, 8] → 16
77
```
78
79
### o
80
Binary function composition (curried compose for exactly 2 functions).
81
82
```javascript { .api }
83
/**
84
* @param {Function} f - Second function to apply
85
* @param {Function} g - First function to apply
86
* @returns {Function} Composed function f(g(x))
87
*/
88
R.o(f, g)
89
90
const slugify = R.o(R.join('-'), R.split(' '));
91
slugify('Hello World'); // => 'Hello-World'
92
93
// More readable than nested calls
94
const getFirstWord = R.o(R.head, R.split(' '));
95
getFirstWord('Hello World'); // => 'Hello'
96
97
// Curried usage
98
const withStringLength = R.o(R.length);
99
const nameLength = withStringLength(R.prop('name'));
100
nameLength({ name: 'Alice' }); // => 5
101
```
102
103
### flow
104
Left-to-right function composition with seed value.
105
106
```javascript { .api }
107
/**
108
* @param {*} seed - Initial value to flow through functions
109
* @param {Array} pipeline - Array of functions to apply in sequence
110
* @returns {*} Final result after applying all functions
111
*/
112
R.flow(seed, pipeline)
113
114
// Process data through a pipeline
115
R.flow(9, [Math.sqrt, R.negate, R.inc]); // => -2
116
// Same as: R.inc(R.negate(Math.sqrt(9)))
117
118
// Transform and validate user input
119
const processInput = input => R.flow(input, [
120
R.trim,
121
R.toLower,
122
R.split(' '),
123
R.filter(R.complement(R.isEmpty)),
124
R.take(3)
125
]);
126
127
processInput(' Hello Beautiful World '); // => ['hello', 'beautiful', 'world']
128
129
// Data-first style (more intuitive than pipe sometimes)
130
R.flow([1, 2, 3, 4, 5], [
131
R.map(R.multiply(2)),
132
R.filter(R.gt(R.__, 5)),
133
R.reduce(R.add, 0)
134
]); // => 18
135
```
136
137
## Currying and Partial Application
138
139
### curry
140
Convert a regular function to a curried function.
141
142
```javascript { .api }
143
/**
144
* @param {Function} fn - Function to curry
145
* @returns {Function} Curried version of the function
146
*/
147
R.curry(fn)
148
149
// Regular function
150
function add3(a, b, c) {
151
return a + b + c;
152
}
153
154
const curriedAdd = R.curry(add3);
155
156
// All these are equivalent:
157
curriedAdd(1, 2, 3); // => 6
158
curriedAdd(1)(2, 3); // => 6
159
curriedAdd(1, 2)(3); // => 6
160
curriedAdd(1)(2)(3); // => 6
161
162
// Partial application becomes natural
163
const add5 = curriedAdd(2)(3); // Waiting for one more argument
164
add5(4); // => 9
165
166
// Real-world example
167
const greet = R.curry((greeting, name, punctuation) =>
168
`${greeting}, ${name}${punctuation}`
169
);
170
171
const sayHello = greet('Hello');
172
const casualGreet = sayHello(R.__, '!');
173
casualGreet('Alice'); // => 'Hello, Alice!'
174
```
175
176
### curryN
177
Curry with explicit arity.
178
179
```javascript { .api }
180
/**
181
* @param {Number} arity - Number of arguments to curry
182
* @param {Function} fn - Function to curry
183
* @returns {Function} Curried function with specified arity
184
*/
185
R.curryN(arity, fn)
186
187
// Useful for variadic functions
188
const sumAll = (...args) => R.sum(args);
189
const curriedSum = R.curryN(3, sumAll);
190
191
curriedSum(1)(2)(3); // => 6
192
curriedSum(1, 2)(3); // => 6
193
194
// Working with functions that have default parameters
195
const withDefaults = (a, b = 10, c = 20) => a + b + c;
196
const curriedWithDefaults = R.curryN(2, withDefaults);
197
198
curriedWithDefaults(5)(15); // => 40 (5 + 15 + 20)
199
```
200
201
### partial, partialRight
202
Partially apply arguments from left or right.
203
204
```javascript { .api }
205
/**
206
* @param {Function} fn - Function to partially apply
207
* @param {Array} leftArgs - Arguments to apply from the left
208
* @returns {Function} Partially applied function
209
*/
210
R.partial(fn, leftArgs)
211
212
const multiply4 = (a, b, c, d) => a * b * c * d;
213
214
// Apply from left
215
const doubleAndSomething = R.partial(multiply4, [2, 3]);
216
doubleAndSomething(4, 5); // => 120 (2 * 3 * 4 * 5)
217
218
// Apply from right
219
const somethingAndTen = R.partialRight(multiply4, [2, 5]);
220
somethingAndTen(3, 4); // => 120 (3 * 4 * 2 * 5)
221
222
// Real-world example: pre-configured API calls
223
const apiCall = (method, url, headers, data) => ({ method, url, headers, data });
224
const postJSON = R.partial(apiCall, ['POST', '/api/users', {'Content-Type': 'application/json'}]);
225
226
postJSON({name: 'Alice'});
227
// => { method: 'POST', url: '/api/users', headers: {...}, data: {name: 'Alice'} }
228
```
229
230
### flip
231
Reverse the order of the first two arguments.
232
233
```javascript { .api }
234
/**
235
* @param {Function} fn - Function whose first two arguments to flip
236
* @returns {Function} Function with first two arguments reversed
237
*/
238
R.flip(fn)
239
240
const divide = (a, b) => a / b;
241
const flippedDivide = R.flip(divide);
242
243
divide(10, 2); // => 5
244
flippedDivide(10, 2); // => 0.2 (2 / 10)
245
246
// Useful with curried functions
247
const subtract = R.subtract; // (a, b) => a - b
248
const subtractFrom = R.flip(subtract); // (a, b) => b - a
249
250
const minus5 = subtract(R.__, 5); // x - 5
251
const subtract5From = subtractFrom(R.__, 5); // 5 - x
252
253
minus5(10); // => 5 (10 - 5)
254
subtract5From(10); // => -5 (5 - 10)
255
```
256
257
## The Placeholder (`R.__`)
258
259
The placeholder allows flexible partial application by marking positions for future arguments.
260
261
```javascript { .api }
262
// Basic placeholder usage
263
const divide = R.divide; // (a, b) => a / b
264
265
const divideBy2 = divide(R.__, 2); // (x / 2)
266
const half = divideBy2(10); // => 5
267
268
const tenDividedBy = divide(10, R.__); // (10 / x)
269
const result = tenDividedBy(2); // => 5
270
271
// Multiple placeholders
272
const clamp = R.clamp; // (min, max, value) => clampedValue
273
274
const clampTo100 = clamp(0, 100, R.__);
275
clampTo100(150); // => 100
276
clampTo100(-10); // => 0
277
278
const clampFrom50 = clamp(50, R.__, R.__);
279
const clampFrom50To200 = clampFrom50(200);
280
clampFrom50To200(75); // => 75
281
282
// Complex example: building specialized filters
283
const filter = R.filter;
284
const propEq = R.propEq;
285
286
const activeUsers = filter(propEq('active', true), R.__);
287
const adminUsers = filter(propEq('role', 'admin'), R.__);
288
289
const users = [
290
{ name: 'Alice', active: true, role: 'admin' },
291
{ name: 'Bob', active: false, role: 'user' },
292
{ name: 'Carol', active: true, role: 'user' }
293
];
294
295
activeUsers(users); // => [Alice, Carol]
296
adminUsers(users); // => [Alice]
297
```
298
299
## Function Transformation
300
301
### unary, binary, nAry
302
Limit function arity.
303
304
```javascript { .api }
305
/**
306
* @param {Function} fn - Function to limit
307
* @returns {Function} Function accepting only specified number of arguments
308
*/
309
R.unary(fn) // Accept only 1 argument
310
R.binary(fn) // Accept only 2 arguments
311
R.nAry(n, fn) // Accept only n arguments
312
313
const logArgs = (...args) => console.log('Arguments:', args);
314
315
const logOne = R.unary(logArgs);
316
const logTwo = R.binary(logArgs);
317
318
logArgs(1, 2, 3, 4); // Logs: Arguments: [1, 2, 3, 4]
319
logOne(1, 2, 3, 4); // Logs: Arguments: [1]
320
logTwo(1, 2, 3, 4); // Logs: Arguments: [1, 2]
321
322
// Useful with higher-order functions
323
['1', '2', '3'].map(parseInt); // => [1, NaN, NaN] (parseInt gets index as 2nd arg)
324
['1', '2', '3'].map(R.unary(parseInt)); // => [1, 2, 3]
325
```
326
327
### once
328
Ensure a function is called only once.
329
330
```javascript { .api }
331
/**
332
* @param {Function} fn - Function to call only once
333
* @returns {Function} Function that can only be called once
334
*/
335
R.once(fn)
336
337
let counter = 0;
338
const increment = () => ++counter;
339
const incrementOnce = R.once(increment);
340
341
incrementOnce(); // => 1
342
incrementOnce(); // => 1 (same result, function not called again)
343
incrementOnce(); // => 1
344
345
// Real-world example: expensive initialization
346
const expensiveSetup = R.once(() => {
347
console.log('Performing expensive setup...');
348
return { initialized: true, timestamp: Date.now() };
349
});
350
351
const result1 = expensiveSetup(); // Logs message, returns object
352
const result2 = expensiveSetup(); // No log, returns same object
353
console.log(result1 === result2); // => true
354
```
355
356
### memoizeWith
357
Cache function results based on arguments.
358
359
```javascript { .api }
360
/**
361
* @param {Function} keyGen - Function to generate cache key from arguments
362
* @param {Function} fn - Function to memoize
363
* @returns {Function} Memoized function
364
*/
365
R.memoizeWith(keyGen, fn)
366
367
// Expensive fibonacci calculation
368
const fib = R.memoizeWith(R.identity, n => {
369
console.log(`Calculating fib(${n})`);
370
return n < 2 ? n : fib(n - 1) + fib(n - 2);
371
});
372
373
fib(10); // Calculates and caches intermediate results
374
fib(8); // Returns cached result immediately
375
376
// Custom cache key generation
377
const expensiveUserLookup = R.memoizeWith(
378
user => `${user.id}-${user.version}`,
379
user => {
380
console.log(`Looking up user ${user.id}`);
381
// Expensive database/API call
382
return { ...user, enrichedData: 'expensive-computation' };
383
}
384
);
385
```
386
387
## Function Analysis and Inspection
388
389
### apply
390
Apply function to argument array.
391
392
```javascript { .api }
393
/**
394
* @param {Function} fn - Function to apply
395
* @param {Array} args - Array of arguments
396
* @returns {*} Result of applying fn to args
397
*/
398
R.apply(fn, args)
399
400
const nums = [1, 2, 3, 4, 5];
401
402
R.apply(Math.max, nums); // => 5 (same as Math.max(...nums))
403
R.apply(R.add, [10, 20]); // => 30
404
R.apply(R.concat, [['a'], ['b']]); // => ['a', 'b']
405
406
// Useful in function compositions
407
const getMaxScore = R.pipe(
408
R.pluck('score'), // Extract scores: [85, 92, 78]
409
R.apply(Math.max) // Find maximum: 92
410
);
411
412
const students = [
413
{ name: 'Alice', score: 85 },
414
{ name: 'Bob', score: 92 },
415
{ name: 'Carol', score: 78 }
416
];
417
418
getMaxScore(students); // => 92
419
```
420
421
### call
422
Call function with provided arguments.
423
424
```javascript { .api }
425
/**
426
* @param {Function} fn - Function to call
427
* @param {...*} args - Arguments to pass to fn
428
* @returns {*} Result of calling fn with args
429
*/
430
R.call(fn, ...args)
431
432
R.call(R.add, 1, 2); // => 3
433
R.call(Math.max, 5, 10, 3); // => 10
434
435
// Useful in converge patterns
436
const average = R.converge(R.divide, [R.sum, R.length]);
437
const weightedAverage = R.converge(
438
R.call,
439
[
440
R.pipe(R.pluck('weight'), R.reduce(R.multiply, 1)),
441
R.pluck('value')
442
]
443
);
444
```
445
446
### juxt
447
Apply multiple functions to same arguments.
448
449
```javascript { .api }
450
/**
451
* @param {Array<Function>} fns - Array of functions to apply
452
* @returns {Function} Function that returns array of results
453
*/
454
R.juxt(fns)
455
456
const getStats = R.juxt([
457
R.length,
458
R.sum,
459
R.mean,
460
Math.min,
461
Math.max
462
]);
463
464
getStats([1, 2, 3, 4, 5]); // => [5, 15, 3, 1, 5]
465
466
// Real-world example: form validation
467
const validateUser = R.juxt([
468
R.pipe(R.prop('email'), R.test(/@/)), // Has @ in email
469
R.pipe(R.prop('age'), R.gte(R.__, 18)), // At least 18
470
R.pipe(R.prop('name'), R.complement(R.isEmpty)) // Name not empty
471
]);
472
473
const user = { email: 'alice@example.com', age: 25, name: 'Alice' };
474
validateUser(user); // => [true, true, true]
475
```
476
477
### converge
478
Apply multiple functions to same input, then combine results.
479
480
```javascript { .api }
481
/**
482
* @param {Function} converging - Function to combine results
483
* @param {Array<Function>} branching - Functions to apply to input
484
* @returns {Function} Converged function
485
*/
486
R.converge(converging, branching)
487
488
// Calculate average: sum / length
489
const average = R.converge(R.divide, [R.sum, R.length]);
490
average([1, 2, 3, 4, 5]); // => 3
491
492
// Create full name from object
493
const fullName = R.converge(R.concat, [
494
R.prop('firstName'),
495
R.pipe(R.prop('lastName'), R.concat(' '))
496
]);
497
498
fullName({ firstName: 'John', lastName: 'Doe' }); // => 'John Doe'
499
500
// Complex example: calculate compound interest
501
const compoundInterest = R.converge(
502
(principal, rate, time, compound) =>
503
principal * Math.pow(1 + rate / compound, compound * time),
504
[
505
R.prop('principal'),
506
R.prop('rate'),
507
R.prop('time'),
508
R.prop('compoundFreq')
509
]
510
);
511
512
const investment = {
513
principal: 1000,
514
rate: 0.05,
515
time: 10,
516
compoundFreq: 4
517
};
518
519
compoundInterest(investment); // => 1643.62
520
```
521
522
## Higher-Order Function Utilities
523
524
### lift, liftN
525
Lift functions to work with wrapped values (applicative functors).
526
527
```javascript { .api }
528
/**
529
* @param {Function} fn - Function to lift
530
* @returns {Function} Lifted function that works with arrays/wrapped values
531
*/
532
R.lift(fn)
533
534
// Lift binary function to work with arrays
535
const liftedAdd = R.lift(R.add);
536
liftedAdd([1, 2], [10, 20]); // => [11, 21, 12, 22]
537
538
// Lift ternary function
539
const liftedAdd3 = R.liftN(3, (a, b, c) => a + b + c);
540
liftedAdd3([1, 2], [10], [100, 200]); // => [111, 211, 112, 212]
541
542
// Real-world: generate test combinations
543
const createUser = R.liftN(3, (name, age, role) => ({ name, age, role }));
544
const testUsers = createUser(
545
['Alice', 'Bob'],
546
[25, 30],
547
['admin', 'user']
548
);
549
// => 8 user objects with all combinations
550
```
551
552
### ap
553
Apply wrapped functions to wrapped values.
554
555
```javascript { .api }
556
/**
557
* @param {Array<Function>} wrappedFns - Array of functions
558
* @param {Array} wrappedVals - Array of values
559
* @returns {Array} Results of applying each function to each value
560
*/
561
R.ap(wrappedFns, wrappedVals)
562
563
const functions = [R.multiply(2), R.add(3)];
564
const values = [1, 2, 3];
565
566
R.ap(functions, values); // => [2, 4, 6, 4, 5, 6]
567
568
// S combinator behavior with two functions
569
R.ap(R.concat, R.toUpper)('ramda'); // => 'ramdaRAMDA'
570
```
571
572
## Debugging and Error Handling
573
574
### tap
575
Execute side effect function then return original value.
576
577
```javascript { .api }
578
/**
579
* @param {Function} fn - Side effect function
580
* @param {*} x - Value to pass through
581
* @returns {*} Original value unchanged
582
*/
583
R.tap(fn, x)
584
585
// Debug pipeline values
586
const debugPipeline = R.pipe(
587
R.map(R.multiply(2)),
588
R.tap(console.log), // Logs: [2, 4, 6, 8]
589
R.filter(R.gt(R.__, 5)),
590
R.tap(console.log), // Logs: [6, 8]
591
R.reduce(R.add, 0)
592
);
593
594
debugPipeline([1, 2, 3, 4]); // => 14
595
596
// Functional logging
597
const logAndReturn = R.tap(x => console.log('Processing:', x));
598
logAndReturn(42); // Logs "Processing: 42", returns 42
599
600
// Side effects in chains
601
const processUser = R.pipe(
602
R.tap(user => analytics.track('user_processed', user)),
603
R.assoc('processed', true),
604
R.tap(user => cache.set(user.id, user))
605
);
606
```
607
608
### tryCatch
609
Functional error handling with try/catch logic.
610
611
```javascript { .api }
612
/**
613
* @param {Function} tryer - Function to attempt
614
* @param {Function} catcher - Function to handle errors
615
* @returns {Function} Function that returns tryer result or catcher result on error
616
*/
617
R.tryCatch(tryer, catcher)
618
619
// Safe JSON parsing
620
const safeParseJSON = R.tryCatch(JSON.parse, R.always({}));
621
safeParseJSON('{"valid": true}'); // => {valid: true}
622
safeParseJSON('invalid json'); // => {}
623
624
// Safe property access
625
const safeProp = key => R.tryCatch(R.prop(key), R.always(null));
626
const getName = safeProp('name');
627
getName({name: 'John'}); // => 'John'
628
getName(null); // => null (no error)
629
630
// API error handling
631
const fetchUserData = id => R.tryCatch(
632
() => api.getUser(id),
633
(error) => ({ error: error.message, id })
634
);
635
636
// Compose with other functions
637
const processData = R.pipe(
638
R.tryCatch(JSON.parse, R.always({})),
639
R.prop('data'),
640
R.defaultTo([])
641
);
642
```
643
644
### addIndex
645
Add index parameter to iteration functions.
646
647
```javascript { .api }
648
/**
649
* @param {Function} fn - Iteration function (like map, filter)
650
* @returns {Function} New function that passes index as second parameter
651
*/
652
R.addIndex(fn)
653
654
// Map with index
655
const mapIndexed = R.addIndex(R.map);
656
mapIndexed((val, idx) => `${idx}: ${val}`, ['a', 'b', 'c']);
657
// => ['0: a', '1: b', '2: c']
658
659
// Filter with index
660
const filterIndexed = R.addIndex(R.filter);
661
filterIndexed((val, idx) => idx % 2 === 0, ['a', 'b', 'c', 'd']);
662
// => ['a', 'c'] (even indices)
663
664
// Real-world: create numbered lists
665
const createNumberedList = R.addIndex(R.map)((item, index) =>
666
`${index + 1}. ${item}`
667
);
668
createNumberedList(['Buy milk', 'Walk dog', 'Code']);
669
// => ['1. Buy milk', '2. Walk dog', '3. Code']
670
671
// Combine with other operations
672
const processWithIndex = R.pipe(
673
R.addIndex(R.map)((item, idx) => ({ ...item, position: idx })),
674
R.filter(R.propEq('active', true))
675
);
676
```
677
678
These function utilities enable powerful abstractions and elegant solutions to complex programming problems through composition, partial application, and higher-order transformations.