Security News
RubyGems.org Adds New Maintainer Role
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Rambda is a lightweight and fast utility library that provides a variety of functions for functional programming in JavaScript. It is a smaller and faster alternative to Ramda, offering a similar API but with a focus on performance and simplicity.
Currying
Currying is a technique of evaluating functions with multiple arguments, one at a time. Rambda's `curry` function allows you to transform a function so that it can be called with fewer arguments than it expects, returning a new function that takes the remaining arguments.
const R = require('rambda');
const add = R.curry((a, b) => a + b);
const add5 = add(5);
console.log(add5(3)); // 8
Composition
Function composition is the process of combining two or more functions to produce a new function. Rambda's `compose` function allows you to create a pipeline of functions that are executed from right to left.
const R = require('rambda');
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;
const addAndMultiply = R.compose(R.multiply(2), R.add(3));
console.log(addAndMultiply(4)); // 14
Cloning
Cloning is the process of creating a deep copy of an object. Rambda's `clone` function allows you to create a deep copy of an object, ensuring that changes to the new object do not affect the original object.
const R = require('rambda');
const obj = {a: 1, b: 2};
const clonedObj = R.clone(obj);
console.log(clonedObj); // {a: 1, b: 2}
Filtering
Filtering is the process of selecting a subset of items from a collection based on a predicate function. Rambda's `filter` function allows you to filter elements in an array or object based on a provided predicate.
const R = require('rambda');
const isEven = n => n % 2 === 0;
const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = R.filter(isEven, numbers);
console.log(evenNumbers); // [2, 4, 6]
Mapping
Mapping is the process of transforming each item in a collection using a provided function. Rambda's `map` function allows you to apply a function to each element in an array or object, returning a new array or object with the transformed elements.
const R = require('rambda');
const double = n => n * 2;
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = R.map(double, numbers);
console.log(doubledNumbers); // [2, 4, 6, 8, 10]
Lodash is a popular utility library that provides a wide range of functions for common programming tasks. It is more feature-rich compared to Rambda but also larger in size. Lodash focuses on performance and ease of use, offering a comprehensive set of tools for working with arrays, objects, strings, and more.
Underscore is another utility library that provides a variety of functional programming helpers. It is similar to Lodash but with a smaller footprint and fewer features. Underscore offers a core set of functions for working with collections, arrays, objects, and functions, making it a good choice for projects that need a lightweight utility library.
Ramda is a functional programming library for JavaScript that emphasizes immutability and pure functions. It offers a similar API to Rambda but with a larger set of functions and a focus on functional programming principles. Ramda is more feature-rich but also larger in size compared to Rambda.
Faster alternative to Ramda
in just 10kB - Documentation
I admire Ramda
, as it is great library in what it does. My main problem was its size. Even custom builds didn't deliver satisfactory results. Also I already had Ramda
habits and I didn't want to switch to Lodash
.
Then I realized that my best solution was to publish a library that recreates the functionality of some Ramda
methods with less code.
const R = require("rambda")
const result = R.compose(
R.join("-"),
R.filter(a => a > 2),
R.flatten,
)([ [1], [2], [3], 4])
console.log(result) // => "3-4"
Use npm i rambda for Webpack
and Node.js
usage
For browser usage include in your HTML
https://cdnjs.cloudflare.com/ajax/libs/rambda/0.8.4/webVersion.js
Rambda shadows only small part of the Ramda's API.
A few things to note:
Rambda's methods should be compatible with most of the basic Ramda's methods. For more complex and Ramda specific methods(such as R.__), you should expect a mismatch.
Rambda's type detect async functions. The returned value is "Async"
Rambda's type detect unresolved Promises
. The returned value is "Promise"
Rambda's map/filter work only for arrays, while Ramda's map/filter accept also objects.
Rambda's equals doesn't protect against circular structures as Ramda.equals does.
Rambda's path, pick and omit accepts both string and array as condition argument.
Rambda's defaultTo approve incoming argument only if it has the same type as the default argument.
Rambda's reverse modifies the array, instead of returning reversed copy of it.
Rambda's partialCurry is not part of Ramda API.
Rambda is tested for compatability with Ramda.flip, as this method could be useful in some cases.
If you need more Ramda methods in Rambda, you may either submit a
PR
or check the extended version of Rambda - Rambdax
add(a: Number, b: Number): Number
R.add(2, 3) // => 5
addIndex(fn: Function): Function
const mapWithIndex = R.addIndex(R.map)
mapWithIndex(
(val, index) => `${val} - ${index}`,
["A", "B", "C"]
) // => ["A - 0", "B - 1", "C - 2"]
adjust(replaceFn: Function, i:Number, arr:Array): Array
It replaces i
index in arr
with the result of replaceFn(arr[i])
.
R.adjust(a => a + 1, 0, [0, 100]) // => [1, 100]
const fn = R.always('foo')
fn() // => 'foo'
any(condition: Function, arr: Array): Boolean
It returns true if at least one member of arr
returns true,
when passed to the condition
function.
R.any(a => a * a > 8)([1, 2, 3]) // => true
R.any(a => a * a > 10)([1, 2, 3]) // => false
append(valueToAppend: any, arr: Array): Array
R.append('foo', ['bar', 'baz']) // => ['foo', 'bar', 'baz']
compose(fn1: Function, ... , fnN: Function): any
It performs right-to-left function composition.
const result = R.compose(
R.map(a => a*2)
R.filter(val => val>2),
)([1, 2, 3, 4])
console.log(result) // => [6, 8]
concat(x: Array|String, y: Array|String): Array|String
It returns new string or array, which is result merging x
and y
.
R.concat([1, 2])([3, 4]) // => [1, 2, 3, 4]
R.concat('foo', 'bar') // => 'foobar'
contains(valueToFind: any, arr: Array): Boolean
It returns true if valueToFind
is part of arr
.
R.contains(2, [1, 2]) // => true
R.contains(3, [1, 2]) // => false
curry(fn: Function): Function
It returns curried version of fn
.
const addFourNumbers = (a, b, c, d) => a + b + c + d
const curriedAddFourNumbers = R.curry(addFourNumbers)
const f = curriedAddFourNumbers(1, 2)
const g = f(3)
g(4) // => 10
defaultTo(defaultArgument: T, inputArgument: any): T
It returns defaultArgument
if inputArgument
is undefined
or the type of inputArgument
is different of the type of defaultArgument
.
It returns inputArgument
in any other case.
R.defaultTo('foo', undefined) // => 'foo'
R.defaultTo('foo')('bar') // => 'bar'
R.defaultTo('foo')(1) // => 'foo'
R.divide(71, 100) // => 0.71
drop(howManyToDrop: Number, arrOrStr: Array|String): Array|String
It returns arrOrStr
with howManyToDrop
items dropped from the left.
R.drop(1, ['foo', 'bar', 'baz']) // => ['bar', 'baz']
R.drop(1, 'foo') // => 'oo'
dropLast(howManyToDrop: Number, arrOrStr: Array|String): Array|String
It returns arrOrStr
with howManyToDrop
items dropped from the right.
R.dropLast(1, ['foo', 'bar', 'baz']) // => ['foo', 'bar']
R.dropLast(1, 'foo') // => 'fo'
endsWith(x: any, arrOrStr: Array|String): Boolean
R.endsWith(
'bar',
"foo-bar"
) // => true
R.endsWith(
'baz',
"foo-bar"
) // => false
equals(a: any, b: any): Boolean
It returns equality match between a
and b
.
It doesn't handle cyclical data structures.
R.equals(1, 1) // => true
R.equals({}, {}) // => false
R.equals([1, 2, 3], [1, 2, 3]) // => true
R.F() // => false
filter(filterFn: Function, arr: Array): Array
Filters arr
throw boolean returning filterFn
const filterFn = a => a % 2 === 0
R.filter(filterFn, [1, 2, 3, 4]) // => [2, 4]
find(findFn: Function, arr: Array): T|undefined
It returns undefined
or the first element of arr
satisfying findFn
.
const findFn = a => R.type(a.foo) === "Number"
const arr = [{foo: "bar"}, {foo: 1}]
R.find(findFn, arr) // => {foo: 1}
findIndex(findFn: Function, arr: Array): Number
It returns -1
or the index of the first element of arr
satisfying findFn
.
const findFn = a => R.type(a.foo) === "Number"
const arr = [{foo: "bar"}, {foo: 1}]
R.find(findFn, arr) // => 1
flatten(arr: Array): Array
R.flatten([ 1, [ 2, [ 3 ] ] ]
// => [ 1, 2, 3 ]
has(prop: String, obj: Object): Boolean
true
if obj
has property prop
.R.has("a", {a: 1}) // => true
R.has("b", {a: 1}) // => false
head(arrOrStr: Array|String): any
It returns the first element of arrOrStr
.
R.head([1, 2, 3]) // => 1
R.head('foo') // => 'f'
ifElse(condition: Function, ifFn: Function, elseFn: Function): Function
It returns function, which expect input
as argument and returns finalResult
.
When the function is called, a value answer
is generated as a result of condition(input)
.
If answer
is true
, then finalResult
is equal to ifFn(input)
.
If answer
is false
, then finalResult
is equal to elseFn(input)
.
const fn = R.ifElse(
x => x > 10,
x => x*2,
x => x*10
)
fn(8) // => 80
fn(11) // => 22
indexOf(valueToFind: any, arr: Array): Number
It returns -1
or the index of the first element of arr
equal of valueToFind
.
R.indexOf(1, [1, 2]) // => 0
init(arrOrStr: Array|String): Array|String
arrOrStr
.R.init([1, 2, 3]) // => [1, 2]
R.init('foo') // => 'fo'
join(separator: String, arr: Array): String
R.join('-', [1, 2, 3]) // => '1-2-3'
last(arrOrStr: Array|String): any
arrOrStr
.R.last(['foo', 'bar', 'baz']) // => 'baz'
R.last('foo') // => 'o'
lastIndexOf(x: any, arr: Array): Number
R.lastIndexOf(1, [1, 2, 3, 1, 2]) // => 3
R.lastIndexOf(10, [1, 2, 3, 1, 2]) // => -1
length(arrOrStr: Array|String): Number
R.length([1, 2, 3]) // => 3
map(mapFn: Function, arr: Array): Array
It returns the result of looping through arr
with mapFn
.
const mapFn = x => x * 2;
R.map(mapFn, [1, 2, 3]) // => [2, 4, 6]
match(regExpression: Regex, str: String): Array
R.match(/([a-z]a)/g, 'bananas') // => ['ba', 'na', 'na']
merge(a: Object, b: Object)
It returns result of Object.assign({}, a, b)
.
R.merge({ 'foo': 0, 'bar': 1 }, { 'foo': 7 })
// => { 'foo': 7, 'bar': 1 }
modulo(a: Number, b: Number): Number
It returns the remainder of operation a/b
.
R.module(14,3) // => 2
multiply(a: Number, b: Number): Number
It returns the result of operation a*b
.
R.module(14,3) // => 2
not(x: any): Boolean
It returns inverted boolean version of input x
.
R.not(true); //=> false
R.not(false); //=> true
R.not(0); //=> true
R.not(1); //=> false
omit(propsToOmit: Array, obj: Object): Object
It returns a partial copy of an obj
with omitting propsToOmit
R.omit(['a', 'd'], {a: 1, b: 2, c: 3}) // => {b: 2, c: 3}
path(pathToSearch: Array|String, obj: Object): any
Retrieve the value at pathToSearch
in object obj
R.path('a.b', {a: {b: 2}}) // => 2
R.path(['a', 'b'], {a: {b: 2}}) // => 2
R.path(['a', 'c'], {a: {b: 2}}) // => undefined
partialCurry(fn: Function|Async, a: Object, b: Object): Function|Promise
When called with function fn
and first set of input a
, it will return a function.
This function will wait to be called with second set of input b
and it will invoke fn
with the merged object of a
over b
.
fn
can be asynchronous function. In that case a Promise
holding the result of fn
is returned.
See the example below:
const fn = ({a, b, c}) => {
return (a * b) + c
}
const curried = R.partialCurry(fn, {a: 2})
curried({b: 3, c: 10}) // => 16
Note that partialCurry
is method specific for Rambda and the method is not part of Ramda's API
You can read my argumentation for creating partialCurry here
pick(propsToPick: Array, obj: Object): Object
It returns a partial copy of an obj
containing only propsToPick
properties.
R.pick(['a', 'c'], {a: 1, b: 2}) // => {a: 1}
pluck(property: String, arr: Array): Array
It returns list of the values of property
taken from the objects in array of objects arr
.
R.pluck('a')([{a: 1}, {a: 2}, {b: 3}]) // => [1, 2]
prepend(x: any, arr: Array): Array
It adds x
to the start of the array arr
.
R.prepend('foo', ['bar', 'baz']) // => ['foo', 'bar', 'baz']
prop(propToFind: String, obj: Object): any
It returns undefined
or the value of property propToFind
in obj
R.prop('x', {x: 100}) // => 100
R.prop('x', {a: 1}) // => undefined
propEq(propToFind: String, valueToMatch: any, obj: Object): Boolean
It returns true if obj
has property propToFind
and its value is equal to valueToMatch
const propToFind = "foo"
const valueToMatch = 0
R.propEq(propToFind, valueToMatch)({foo: 0}) // => true
R.propEq(propToFind, valueToMatch)({foo: 1}) // => false
range(start: Number, end: Number): Array
It returns a array of numbers from start
(inclusive) to end
(exclusive).
R.range(0, 2) // => [0, 1]
reduce(iteratorFn: Function, accumulator: any, array: Array): any
It returns a single item by iterating through the list, successively calling the iterator function iteratorFn
and passing it an accumulator
value and the current value from the array, and then passing the result to the next call.
The iterator function behaves like the native callback of the Array.prototype.reduce
method.
const iteratorFn = (acc, val) => acc + val
R.reduce(iteratorFn, 1, [1, 2, 3]) // => 7
repeat(valueToRepeat: T, num: Number): Array
R.repeat('foo', 2) // => ['foo', 'foo']
replace(strOrRegex: String|Regex, replacer: String, str: String): String
Replace strOrRegex
found in str
with replacer
R.replace('foo', 'bar', 'foo foo') // => 'bar foo'
R.replace(/foo/, 'bar', 'foo foo') // => 'bar foo'
R.replace(/foo/g, 'bar', 'foo foo') // => 'bar bar'
!!! It modifies the array instead of returning new copy, as original Ramda
method does.
const arr = [1, 2]
R.reverse(arr)
console.log(arr) // => [2, 1]
sort(sortFn: Function, arr: Array): Array
It returns copy of arr
sorted by sortFn
.
sortFn
must return Number
const sortFn = (a, b) => a - b
R.sort(sortFn, [3, 1, 2]) // => [1, 2, 3]
sortBy(sortFn: Function, arr: Array): Array
It returns copy of arr
sorted by sortFn
.
sortFn
must return value for comparison
const sortFn = obj => obj.foo
R.sortBy(sortFn, [
{foo: 1},
{foo: 0}
])
// => [{foo: 0}, {foo: 1}]
split(separator: String, str: String): Array
R.split('-', 'a-b-c') // => ['a', 'b', 'c']
splitEvery(sliceLength: Number, arrOrString: Array|String): Array
arrOrStr
into slices of sliceLength
R.splitEvery(2, [1, 2, 3]) // => [[1, 2], [3]]
R.splitEvery(3, 'foobar') // => ['foo', 'bar']
startsWith(x: any, arrOrStr: Array|String): Boolean
R.endsWith(
'bar',
"foo-bar"
) // => true
R.endsWith(
'baz',
"foo-bar"
) // => false
subtract(a: Number, b: Number): Number
R.subtract(3, 1) // => 2
tail(arrOrStr: Array|String): Array|String
arrOrStr
R.tail([1, 2, 3]) // => [2, 3]
R.tail('foo') // => 'oo'
take(num: Number, arrOrStr: Array|String): Array|String
num
elements of arrOrStr
.R.take(1, ['foo', 'bar']) // => ['foo']
R.take(2, ['foo']) // => 'fo'
takeLast(num: Number, arrOrStr: Array|String): Array|String
num
elements of arrOrStr
.R.takeLast(1, ['foo', 'bar']) // => ['bar']
R.takeLast(2, ['foo']) // => 'oo'
test(regExpression: Regex, str: String): Boolean
str
matches regExpression
R.test(/^f/, 'foo') // => true
R.test(/^f/, 'bar') // => false
toLower(str: String): String
R.toLower('FOO') // => 'foo'
toUpper(str: String): String
R.toUpper('foo') // => 'FOO'
trim(str: String): String
R.trim(' foo ') // => 'foo'
type(a: any): String
R.type(() => {}) // => "Function"
R.type(async () => {}) // => "Async"
R.type([]) // => "Array"
R.type({}) // => "Object"
R.type('foo') // => "String"
R.type(1) // => "Number"
R.type(true) // => "Boolean"
R.type(null) // => "Null"
R.type(/[A-z]/) // => "RegExp"
const delay = ms => new Promise(resolve => {
setTimeout(function () {
resolve()
}, ms)
})
R.type(delay) // => "Promise"
uniq(arr: Array): Array
It returns a new array containing only one copy of each element in arr
.
R.uniq([1, 1, 2, 1]) // => [1, 2]
R.uniq([1, '1']) // => [1, '1']
update(i: Number, replaceValue: any, arr: Array): Array
It returns a new copy of the arr
with the element at i
index
replaced with replaceValue
.
R.update(0, "foo", ['bar', 'baz']) // => ['foo', baz]
values(obj: Object): Array
It returns array with of all values in obj
.
R.values({a: 1, b: 2}) // => [1, 2]
R.T() // => true
The following methods are included as it costs next to nothing to add them.
Note that the following list of methods are not part of Ramda
API.
includes(x: any, arrOrStr: Array|String): Boolean
R.includes(1, [1, 2]) // => true
R.includes('oo', 'foo') // => true
R.includes('z', 'foo') // => false
padEnd(x: Number, str: String): String
R.padEnd(3, 'foo') // => 'foo '
padStart(x: Number, str: String): String
R.padStart(3, 'foo') // => ' foo'
R.toString([1, 2]) // => '1,2'
I haven't tested it fully, but the partial test shows that Ramda definitions can be used.
You need to replace declare module ramda
with declare module rambda
on line 10 and store the file as rambda.js
in your flow-typed folder
npm test
before npm publish
the hard wayR.always
, R.T
and R.F
concat
, padStart
, padEnd
, lastIndexOf
, toString
, reverse
, endsWith
and startsWith
methodsR.ifElse
R.not
, R.includes
| Take string as condition for R.pick
and R.omit
R.values
R.omit
R.curry
, which used to return incorrectly function
when called with more argumentses2015
; Approve PR #10 - add R.addIndex
to the APIPromise
support for R.type
R.reduce
to the APIcurry
to partialCurry
; add new method curry
, which works just like Ramda's curry
docsify
If you want to add another Ramda
method to the API, please feel free to submit a PR
.
The only requirement is the new method to have exact or very close implementation compared to the corresponding Ramda
method.
I give you example steps of the PR
process.
Create a method file in
modules
folder.
If the new method is R.endsWith
, then the created file will be ./modules/endsWith.js
Write the function declaration and function's logic.
function endsWith(x, arrOrStr){
return arrOrStr.endsWith(x)
}
Any method, which takes more than one argument, should be curried.
We can use the standard curring used throughout Rambda
.
function endsWith(x, arrOrStr){
if(arrOrStr === undefined){
return arrOrStrHolder => endsWith(x, arrOrStrHolder)
}
return arrOrStr.endsWith(x)
}
module.exports = endsWith
Or we can also use R.curry
, but it is not as performant as the example above.
const curry = require('./curry')
function endsWith(x, arrOrStr){
if(arrOrStr === undefined){
return holder => endsWith(x, arrOrStr)
}
return arrOrStr.endsWith(x)
}
module.exports = curry(endsWith)
Edit
rambda.js
file
Exported methods are sorted alphabetically
exports.dropLast = require("./modules/dropLast")
exports.endsWith = require("./modules/endsWith")
exports.equals = require("./modules/equals")
Write your test cases
Create file endsWith.js
in folder __tests__
const R = require('../rambda')
test('endsWith', () => {
expect(R.endsWith('oo')('foo')).toBeTruthy()
})
Run
npm test
to validate your tests
Edit
./README.md
to add documentation
Note that your documentation should match the pattern visible across ./README.md
Lint your files
npm run lint modules/endsWith.js
npm run lint __tests__/endsWith.js
Submit PR
Expect response within 2 days.
Projects using Rambda
Articles about Rambda
FAQs
Lightweight and faster alternative to Ramda with included TS definitions
The npm package rambda receives a total of 551,776 weekly downloads. As such, rambda popularity was classified as popular.
We found that rambda demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.
Security News
Research
Socket's threat research team has detected five malicious npm packages targeting Roblox developers, deploying malware to steal credentials and personal data.