I often find myself needing a function like map
or filter
, but unable/unwilling to import an entire library like the famous Lodash or Underscore (whichever you fancy). There can be many reasons for this, but my primary reason is usually that I am building a small plugin to handle some operation for someone, and it is entirely unreasonable of me to expect a client to important an entire library, five times the size of my little plugin, just so I can filter an array properly (or whatever else I need that library for).
So I often have to cherry pick functions from those libraries, but they are not built in a modular way internally. Functions inside the library are highly dependent on other functions in order to save bytes and speed. For instance you can't copy map
over without also copying each
. So instead, here are some functions that I wrote, that can all (save a few very obvious ones) be exported "as is" into other code. Change them, rename them, make them faster, do whatever you want with them. They are just a starting point, and a small building block for bigger things.
For this reason, I haven't added this "project" (seriously, it's just a file of code) to Bower or any some such. It's not meant to be included as a full library (if that's what you need, then use another library), but just some code that you can search through and copy/paste the stuff you need, and pretend you wrote it yourself.
Important: Since this is not a library meant to be included in any project as is, I will (most likely) at any point in time move things around, change implementations and/or functionality, and even names of functions.
We all love examples. Here are some of how this is meant to be used.
I consider compose
and autoCurry
(which is dependent on curry
) to be cornerstones in coding, and would include them in any project. Example 2 and 3 below reflect usage of them. As examples often are, these are a bit contrived.
Writing a small plugin, you need a function that flattens a list of lists. You know how to do this... sorta... but it'd be nice if someone else had already done it. So you find it in here and copy/paste it into your own project. No sweat.
var flatten = function(xs) { /* find the code in cherries.js ... */ };
And now you're flattening arrays all day long!
Let's find the average age of a list of people that looks like this:
var people = [ {name: 'Bob Roberts', age: 31, gender: 'm'}, {name: 'Julia Smith', age: 63, gender: 'f'}, {name: 'Ken Paulsen', age: 23, gender: 'm'}, {name: 'Lisa Erikson', age: 9, gender: 'f'} ];
For this, we could use pluck
and mean
.
/* I assume compose and autoCurry is already available */ var pluck = autoCurry( /* code defining pluck */ ), mean = /* code defining mean */;
And we compose the function we need,
Notice that since pluck
is autoCurried, calling it with just one argument (it needs two) will return a function waiting for a list of objects (how convenient) that returns a list of whatever we plucked out (in this case the age).
// Compose a function that first plucks out the age into a list, and then // takes the mean of that list var getMeanOfAges = compose( mean, pluck('age') ); // Try out our function getMeanOfAges(people) // 31.5
Of course, if you only need pluck
once, and to do only the above, then you'd probably not include autoCurry
and pluck
, but rather just define your own function and use that instead. Something like this:
// Quick adaptation of the `pluck` within cherries var pluckAge = function(xs){ var result = [], i = -1, len = xs.length; while (++i < len) { result.push(xs[i].age) } return result; }; var getMeanOfAges = compose(mean, pluckAge);
You're writing a little plugin for a client and you need an easy way to pull some data representing people out that comes in a list form, you want an object, and you want to pick out certain data.
Here's a sample list we could get from a server or whatever.
var people = [ ['Bob Roberts', 31, 'm'], ['Julia Smith', 63, 'f'], ['Ken Paulsen', 23, 'm'], ['Lisa Erikson', 9, 'f'] ];
First you pick out the functions you need from this library and define them using autoCurry
/* compose and autoCurry assumed */ var pick = autoCurry( /* code defining pick */ ), map = autoCurry( /* code defining map */ ), listsToObj = autoCurry( /* code defining listsToObj */ );
Then we build a function that turns people
into an object and gives us a list of of whatever we need
Like in the previous example, the functions are autoCurried, so calling them with less arguments than they expect (map
, pick
, and listsToObj
each need two arguments, we're calling them with one) means we get a function back that is waiting for the last argument.
var getNameAndAgeOf = compose( map( pick(['name', 'age']) ), map( listsToObj(['name', 'age', 'gender']) ) );
Calling this function on people
...
getNameAndAgeOf(people) // [{"name":"Bob Roberts","age":31}, ... ]
We can make it a bit more general use actually...
var getThisOfThat = function(xs) { return compose( map( pick(xs) ), map( listsToObj(['name', 'age', 'gender']) ) ); }; // Make a function that pulls out name and gender var getNameAndGender = getThisOfThat(['name', 'gender']) // Try out our new function getNameAndGender(people) // [{"name":"Bob Roberts","gender":"m"}, ... ]
See the examples at lt
for some more examples of usage.
Here's a list of each of the functions currently defined. Remember, dig into the code and pull out the bits you need. Don't just import everything; there are better libraries for that.
Jump to: id, isType, replicate, dot, pluck, each, map, filter, reject, partition, find, head, tail, last, initial, take, drop, takeWhile, dropWhile, span, breakList, empty, reverse, foldl, foldr, concat, flatten, any, all, sort, sortBy, sum, product, even, odd, lt, gt, eq, lteq, gteq, mean, maximum, minimum, zip, zipWith, keys, values, pairsToObj, objToPairs, listsToObj, objToLists, extend, extendAll, pick, omit, compose, curry, autoCurry.
id
, kind of does nothing.
a → a
id('foo') // 'foo' id([]) // []
isType
. You can check if a variable has a certain type with this.
"a" → a → Boolean
isType('Undefined', undefined) // true isType('Boolean', false) // true isType('Number', 3) // true isType('String', 'foo') // true isType('Object', {}) // true isType('Array', []) // true
replicate
. Creates an array with however many items of whatever you want.
Number → a → [a]
replicate(3, 'foo') // ['foo', 'foo', 'foo'] replicate(2, true) // [true, true]
dot
. Gets a property of the passed in object.
"a" → {a:b} → b
dot('age', {name: 'Simon', age: 45}) // 45 dot('status', {status: false}) // false
pluck
. Does the same as dot
, but for an array of objects.
"a" → [{a:b}] → [b]
pluck('age', [{name: 'Simon', age: 45}, {name: 'Bob', age: 12}]) // [45, 12] pluck('status', [{status: false}]) // [false]
each
. Your general each
or forEach
function. Nothing spectacular.
Notice, that as it is, it will default to the browser's native forEach
if it exists, in which case it will return undefined
and not the array you passed in.
Function → a → a
each(alert, [1, 2, 3]) // will call alert on each number each(alert, {one: 1, two: 2}) // once more :(
map
. A map function. Like each
it works on both arrays and objects (their values).
Function → a → a
map(function(x){ return x+1 }, [1, 2, 3]) // [2, 3, 4] map(function(x){ return x+1 }, {one: 1, two: 2}) // {one: 2, two: 3}
filter
.
Function → a → a
function even(x) { return x%2 === 0 } filter(even, [1, 2, 3, 4]) // [2, 4] filter(even, {one: 1, two: 2, three: 3}) // {two: 2}
reject
. Opposite filter
.
Function → a → a
reject(even, [1, 2, 3, 4]) // [1, 3] reject(even, {one: 1, two: 2, three: 3}) // {one: 1, three: 3}
partition
. Kind of like filter
and reject
in one. It returns an array with two items: first one of filtered items, second of reject items.
Fucntion → a → [a, a]
partition(even, [1, 2, 3, 4]) // [[2, 4], [1, 3]] partition(even, {one: 1, two: 2, three: 3}) // [{two: 2}, {one: 1, three: 3}]
find
. Finds the first value that matches your function and returns that.
Function → a → a
find(even, [1, 2, 3, 4]) // 2 find(even, {one: 1, two: 2, three: 3}) // 2
head
. Returns the "head" or first value of a list.
[a] → a
head([1, 2, 3, 4]) // 1
tail
. Returns the "tail" or everything but first value of a list.
[a] → [a]
tail([1, 2, 3, 4]) // [2, 3, 4]
last
. Returns the "last" or last value of a list.
[a] → a
last([1, 2, 3, 4]) // 4
initial
. Returns the "initial" part of a list, or everything but the last value.
[a] → a
initial([1, 2, 3, 4]) // [1, 2, 3]
take
. Takes n
number of values from a list.
Number → [a] → [a]
take(2, [1, 2, 3, 4]) // [1, 2]
drop
. Drops n
number of values from a list and returns the rest.
Number → [a] → [a]
drop(2, [1, 2, 3, 4]) // [3, 4]
takeWhile
. Copies your array until the values no longer returns truthy from your applied function.
Function → [a] → [a]
takeWhile(even, [2, 4, 0, 3, 4, 2]) // [2, 4, 0]
dropWhile
. Drops the values of a list until they no longer return truthy from your function. From that, it copies your array.
Function → [a] → [a]
dropWhile(even, [2, 4, 0, 3, 4, 2]) // [3, 4, 2]
span
is to takeWhile
and dropWhile
what partition
is to filter
and reject
.
Function → [a] → [[a], [a]]
span(even, [2, 4, 0, 3, 4, 2]) // [[2, 4, 0], [3, 4, 2]]
breakList
breaks a list into two where the function returns truthy.
Function → [a] → [[a], [a]]
breakList(even, [1, 3, 2, 4, 0, 3]) // [[1, 3], [2, 4, 0, 3]]
empty
checks if an array or object is empty.
a → Boolean
empty([]) // true empty({}) // true empty({one: 1}) // false
reverse
returns a reversed shallow copy of the list.
[a] → [a]
reverse([1, 2, 3]) // [3, 2, 1]
foldl
is your standard reduce
function.
Function → a → [a] → a
function add(x, y) { return x + y } foldl(add, 7, [1, 2, 3]) // 13
foldr
is your standard reduceRight
function.
Function → a → [a] → a
function subtract(x, y) { return x - y } foldl(subtract, 7, [1, 2, 3]) // 1
concat
takes a list of lists and concatenates them. Notice it only does this one level down.
[[a],[a]] → [a, a]
concat([[1, 2, 3], ['a', 'b']]) // [1, 2, 3, 'a', 'b']
flatten
takes a list of lists and flattens them.
[[a]] → [a]
flatten([ 1, [ [2], 3 ], [4, [[5]]], [{one: 1}, "hello"] ]) // [1, 2, 3, 4, 5, {one: 1}, "hello"]
any
can tell you if any values in a list returns truthy from a function.
Function → [a] → Boolean
any(even, [1, 2, 3]) // true any(even, [1, 3, 77]) // false
all
can tell you if all values in a list returns truthy from a function.
Function → [a] → Boolean
all(even, [1, 2, 3]) // false all(odd, [1, 3, 77]) // true
sort
sorts an array of numbers. (Have a look at the implementation, it is highly subjective what you will expect this to do).
[Number] → [Number]
sort([2, 12, 1]) // [1, 2, 12]
sortBy
sorts an array according to a provided function.
Function → [a] → [a]
sortBy(function(a, b) { return b-a }, [2, 12, 1]) // [12, 2, 1]
sum
. Sums up numbers in an array.
[Number] → Number
sum([3, 4, 5]) // 12
product
.
[Number] → Number
product([3, 4, 5]) // 60
even
. Checks if a number is even.
Number → Boolean
even(2) // true
odd
. Checks if a number is odd.
Number → Boolean
odd(3) // true
lt
. "Less than". Notice that the arguments are in "reversed" order so they make more sense when curried.
Number → Number → Boolean
lt(3, 2) // true // Curried var lt3 = curry(lt, 3); lt3(2) // true filter(lt3, [1, 5, 3, 9, 0, 2, 4, 2]) // [1, 0, 2, 2] // Or better yet, redefine `lt` with autoCurry for better reuse lt = autoCurry(lt); listOfNum = [1, 5, 3, 9, 0, 2, 4, 2]; // Just because we will need it filter(lt(3), listOfNum) // [1, 0, 2, 2] filter(lt(5), listOfNum) // [1, 3, 0, 2, 4, 2] // Let's go crazy var filterLt = autoCurry(function(n, xs) { return filter(lt(n), xs); // assuming `lt` is still autoCurried }); // Here we call `filterLt` twice with one argument each filterLt(3)(listOfNum) // [1, 0, 2, 2] // Here we call `filterLt` once with two arguments filterLt(5, listOfNum) // [1, 3, 0, 2, 4, 2]
gt
. "Greater than"
Number → Number → Boolean
gt(3,4) // true
eq
. "Equal" (This actually compares any two values using ===
)
Number → Number → Boolean
eq(3,3) // true
lteq
. "Less than, Equal" or <=
Number → Number → Boolean
lteq(3,2) // true
gteq
. "Greather than, Equal" or >=
Number → Number → Boolean
gteq(3,3) // true
mean
. Finds the mean of the numbers in a list.
[Number] → Number
mean([1, 2, 3, 4]) // 2.5
maximum
finds the highest number in a list.
[Number] → Number
maximum([6, 7, 2, 3]) // 7
minimum
finds the lowest number in a list.
[Number] → Number
minimum([6, 7, 2, 3]) // 2
zip
takes two arrays and "zips" them together.
[a] → [b] → [[a, b]]
zip(['a', 'b', 'c'], [1, 2, 3]) // [['a', 1], ['b', 2], ['c', 3]]
zipWith
takes a function and two arrays and zips them together using that function.
Function → [a] → [a] → [a]
function add(x, y) { return x+y } zipWith( add, [1, 2, 3], [7, 8, 9]) // [8, 10, 12]
keys
returns an array with the keys of an object.
{a:b} → [a]
keys({one: 1, two: 2, three: 3}) // ['one', 'two', 'three']
values
returns an array with the values of an object.
{a:b} → [b]
values({one: 1, two: 2, three: 3}) // [1, 2, 3]
pairsToObj
. Takes an array of arrays with two values and turns them into an object.
[[a, b]] → {a:b}
pairsToObj([['a', 1], ['b', 2], ['c', 3]]) // {a: 1, b: 2, c: 3}
objToPairs
does the opposite as above.
{a:b} → [[a, b]]
objToPairs({a: 1, b: 2, c: 3}) // [['a', 1], ['b', 2], ['c', 3]]
listsToObj
takes two lists and returns an object with one arrays' values as keys and the other as the values.
[a] → [b] → {a:b}
listsToObj(['a', 'b', 'c'], [1, 2, 3]) // {a: 1, b: 2, c: 3}
objToLists
does the opposite as above.
{a:b} → [[a], [b]]
objToLists({a: 1, b: 2, c: 3}) // [['a', 'b', 'c'], [1, 2, 3]]
extend
takes two objects and "extends" them to a shallow clone.
Because it takes a fixed number of arguments, it is easy to curry, whereas extendAll
is a bit tricky.
{a} → {a} → {a}
extend({one: 1, two: 2, three: 3}, {three: 33, four: 44}) // {one: 1, two: 2, three: 33, four: 44}
extendAll
takes two or more objects and returns one object with the keys and values from all the others. (The copy is shallow)
Because it takes a variable number of arguments, this function is a bit tricky to curry.
{a} → {a} → ... → {a}
extendAll({one: 1, two: 2, three: 3}, {three: 33, four: 44}, {four: 'f', five: 'g'}) // {one: 1, two: 2, three: 33, four: 'f', five: 'g'}
pick
returns an object with the keys you asked for.
[a] → {a} → {a}
pick(['one', 'three'], {one: 1, two: 2, three: 3}) // {one: 1, three: 3}
omit
. Opposite pick
.
[a] → {a} → {a}
omit(['one', 'three'], {one: 1, two: 2, three: 3}) // {two: 2}
compose
. Composes functions together. Takes an optional number of arguments, so this can't be curried.
Perhaps ironically, this function becomes a lot more powerful when other functions are curried.
Function → Function → ... → Function
function getName(o) { return o['name'] } var alertName = compose(alert, getName) alertName({name: 'Simon', age: 45}) // alerts 'Simon' // If `dot` is autoCurried (dot = autoCurry(dot);) then var alertName2 = compose(alert, dot('name')); alertName2({name: 'Simon', age: 45}) // alerts 'Simon'
curry
. Returns a curried version of a function. This is not really meant to be used as is, but rather autoCurry
is dependent on it.
Function → a → ... → Function
function add(x, y) { return x+y } var add1 = curry(add, 1) add1(2) // 3 add1(5) // 6
autoCurry
. Takes a function and an optional number, and returns a curried version of that function. The number, if applied, tells autoCurry
how many arguments it needs to be fully applied. (if not applied it will deduct it using fn.length
.
I personally define almost all my function inside this function.
Function → (Number →) Function
var cAdder = autoCurry( function(x, y) { return x+y } ); var add1 = cAdder(1); add1(2) // 3 add1(5) // 6 var add5 = cAdder(5); add5(2) // 7 add5(5) // 10
applyl
. Takes a function and a list of arguments, and returns a function that waits for the last arguments. This function is a bit like curry
, except curry will wait for all arguments to arrive, while this will run the function next time called, no matter what
Function → [a] → Function
var calc = function(x, y, z) { return (x+y)*z }; var times3 = applyl(calc, [1, 2]); times3(4) // 12 times3(7) // 21 times3() // NaN var isBool = applyl(isType, ['Boolean']); isBool(false) // true isBool('Hello') // false
applyr
. Takes a function and a list of arguments, and returns a function that waits for the "first" arguments.
Function → [a] → Function
var calc = function(x, y, z) { return (x+y)*z }; var add1AndMultiplyBy2 = applyr(calc, [1, 2]); add1AndMultiplyBy2(3) // 8 add1AndMultiplyBy2(21) // 44