TL;DR ...
const activeUsers = (input) => {
const is = field => item => ( item[field] )
return input.filter(is('isActive'))
}
The
is
function is a curried function that checks if a given value of an attribute is true. Then theis
function can be simply passed to the filter function with a single parameter as the field that is used to check corresponding value of the attribute.
You can download the project files here
Curry Intro
A function is curried when the arguments are passed one at a time... sounds funny or vague ? Let's demonstrate a curried function with an example instead.
Here is a basic add function that +
the arguments:
const add = (a, b) => (a + b)
add(1,2) //-> 3
Now, you can curry the add
function and pass the arguments one at a time:
const add = a => b => (a + b)
add(1)(2) // -> 3
In the version above, we are defining the function add
that takes only one parameter. Then it returns another function which also takes another parameter (the second parameter to add), and just returns the result of +
ing the inputs, a and b. And when we call the add
function for the first time, it returns another function, hence add(1)(2)
notation. You could assign the result of calling add for the first time to a variable if you want: const add1 = add(1)
and then call the other parameter with this function: add1(2) // -> 3
There are many examples of currying out there that could get very complicated very easily, but let's take a look at a very basic example.
Let's say you have an array of user objects and you want to filter out the users that are not active. So given:
const usersFixture = [
{ name: 'Amin', lastname: 'Meyghani', isActive: true },
{ name: 'Audrey', lastname: 'Pike', isActive: true },
{ name: 'Al', lastname: 'Meyghani', isActive: false },
{ name: 'Mays', lastname: 'Meyghani', isActive: false }
]
the output would be:
const expected = [
{ name: 'Amin', lastname: 'Meyghani', isActive: true },
{ name: 'Audrey', lastname: 'Pike', isActive: true }
]
So, the resultant array only contains the users that are active. Now, you can simply do usersFixture.filter(user => user.isActive)
and it will work, but let's see if we can make a curried function and pass it to the filter so that it can take care of the heavy lifting for us.
Set up
Because we are responsible developers, we should set up our tests first. So make a folder on your desktop, navigate to it, set up a npm package:
mkdir ~/Desktop/curry-example && cd $_ && npm init
accept all the defaults and then install tape
:
npm i tape -D
npm i faucet -g # optional
then make a file called main.test.js
:
touch main.test.js
Now we are ready to go! First, let's require tape
and set up our fixtures:
const test = require('tape').test;
const fixtures = {};
const setup = () => {
fixtures.input = [
{name: 'Amin', lastname: 'Meyghani', isActive: true},
{name: 'Audrey', lastname: 'Pike', isActive: true},
{name: 'Al', lastname: 'Meyghani', isActive: false},
{name: 'Mays', lastname: 'Meyghani', isActive: false}
]
fixtures.output = [
{name: 'Amin', lastname: 'Meyghani', isActive: true},
{name: 'Audrey', lastname: 'Pike', isActive: true}
]
return fixtures
}
const teardown = () => (fixture = null)
/**
* under test
*/
const activeUsers = (input) => {
}
test('The activeUsers method', assert => {
const fixtures = setup()
const actual = activeUsers(fixtures.input)
const expected = fixtures.output
assert.deepEqual(actual, expected, 'should return active uesrs')
teardown()
assert.end()
})
now we are going to run this with node main.test.js
and see our test fail with a big red failure:
TAP version 13
# The activeUsers method
not ok 1 should return active uesrs
---
operator: deepEqual
expected: |-
[ { isActive: true, lastname: 'Meyghani', name: 'Amin' }, { isActive: true, lastname: 'Pike', name: 'Audrey' } ]
actual: |-
undefined
at: Test.<anonymous> (/Users/amin.meyghani/Desktop/curry/main.test.js:28:10)
...
1..1
# tests 1
# pass 0
# fail 1
Now, our job is to write the body of the activeUsers
and make this pass of course :) Let's enter the zen (TDD) mode, by installing nodemon
and watching the test as we work:
npm i nodemon -g # install nodemon
nodemon main.test.js # run the test and watch the files.
Now, every time you save the file, it is going to run your test. So, let's make the test pass with minimal effort, which might look a bit dumb, but we are in zen mode, soooo ....
const activeUsers = (input) => {
const result = [];
input.splice(2, 2);
return result.concat(input);
}
this will make our test pass, but obviously this is not the solution, because the indexes is not what we should rely on. Now, let's improve it and use the filter method on the array to filter out the inactive users:
const activeUsers = (input) => {
return input.filter(item => item.isActive)
}
Now, we are going to write a curried function called is
which just checks the value of a given key:
const is = field => item => ( item[field] )
and now, we can just pass this function to the filter:
const activeUsers = (input) => {
const is = field => item => ( item[field] )
return input.filter(is('isActive'))
}
and our test should be passing now:
TAP version 13
# The activeUsers method
ok 1 should return active uesrs
1..1
# tests 1
# pass 1
# ok
and the full version of main.test.js
:
const test = require('tape').test;
const fixtures = {};
const setup = () => {
fixtures.input = [
{name: 'Amin', lastname: 'Meyghani', isActive: true},
{name: 'Audrey', lastname: 'Pike', isActive: true},
{name: 'Al', lastname: 'Meyghani', isActive: false},
{name: 'Mays', lastname: 'Meyghani', isActive: false}
]
fixtures.output = [
{name: 'Amin', lastname: 'Meyghani', isActive: true},
{name: 'Audrey', lastname: 'Pike', isActive: true}
]
return fixtures
}
const teardown = () => (fixture = null)
/**
* under test
*/
const activeUsers = (input) => {
const is = field => item => ( item[field] )
return input.filter(is('isActive'))
}
test('The activeUsers method', assert => {
const fixtures = setup()
const actual = activeUsers(fixtures.input)
const expected = fixtures.output
assert.deepEqual(actual, expected, 'should return active uesrs')
teardown()
assert.end()
})