I have a list of people in this structure:
const people = [
{name: 'jenny', friends: ['jeff']},
{name: 'frank', friends: ['jeff', 'ross']},
{name: 'sarah', friends: []},
{name: 'jeff', friends: ['jenny', 'frank']},
{name: 'russ', friends: []},
{name: 'calvin', friends: []},
{name: 'ross', friends: ['frank']},
];
I would like to filter out people two ways: with and without friends; moreover I would like the Predicate of the Array.filter
to be lifted, like so:
const peopleWithoutFriends = people.filter(withoutFriends);
console.log(peopleWithoutFriends);
const peopleWithFriends = people.filter(withFriends);
console.log(peopleWithFriends);
I can achieve this behavior by explicitly writing a by
function like this:
const by = x => i => {
return Boolean(get(i, x));
};
const withFriends = by('friends.length');
const peopleWithFriends = people.filter(withFriends);
console.log(peopleWithFriends);
Problem: If I wanted the inverse I would need to explicitly write a whole new function for peopleWithoutFriends
const notBy = x => i => {
return !Boolean(get(i, x));
};
const withOutFriends = notBy('friends.length');
const peopleWithoutFriends = people.filter(withOutFriends);
I do not want to write my by
function twice. I would rather compose smaller functions together.
How can I write and use small functions like: flow
Boolean
get
curry
not
and compose withFriends
and withOutFriends
Predicates for my Array.filter over the list of people
.
Repl: https://repl.it/@matthewharwood/ChiefWelloffPaintprogram
const {flow, get, curry} = require('lodash');
const people = [
{name: 'jenny', friends: ['jeff']},
{name: 'frank', friends: ['jeff', 'ross']},
{name: 'sarah', friends: []},
{name: 'jeff', friends: ['jenny', 'frank']},
{name: 'russ', friends: []},
{name: 'calvin', friends: []},
{name: 'ross', friends: ['frank']},
];
const not = i => !i;
const withFriends = i => flow(
Boolean,
get(i, 'friends.length'), // arity of this is 2 so might be harder to lift, is it possible tho with curry?
); // No idea what i'm doing here.
const peopleWithFriends = people.filter(withFriends);
console.log(peopleWithFriends);
const withoutFriends = flow(not, withFriends);
const peopleWithoutFriends = people.filter(withoutFriends);
console.log(peopleWithoutFriends);
Since the result of with/without friends functions is a Boolean, you can negate (or complement) the result of the one to get the other. In addition the arity of the functions is 1 (the object they operate on).
Lodash/fp:
const { flow, get, isEmpty, negate } = _;
const people = [
{name: 'jenny', friends: ['jeff']},
{name: 'frank', friends: ['jeff', 'ross']},
{name: 'sarah', friends: []},
{name: 'jeff', friends: ['jenny', 'frank']},
{name: 'russ', friends: []},
{name: 'calvin', friends: []},
{name: 'ross', friends: ['frank']},
];
const withoutFriends = flow(get('friends'), isEmpty); // create a function that gets the friends array, and check if it is empty
const withFriends = negate(withoutFriends); // negate the result of withoutFriends
const peopleWithFriends = people.filter(withFriends);
console.log(peopleWithFriends);
const peopleWithoutFriends = people.filter(withoutFriends);
console.log(peopleWithoutFriends);
<script src='https://cdn.jsdelivr.net/g/lodash@4(lodash.min.js+lodash.fp.min.js)'></script>
Ramda:
const { pipe, prop, isEmpty, complement } = R;
const people = [
{name: 'jenny', friends: ['jeff']},
{name: 'frank', friends: ['jeff', 'ross']},
{name: 'sarah', friends: []},
{name: 'jeff', friends: ['jenny', 'frank']},
{name: 'russ', friends: []},
{name: 'calvin', friends: []},
{name: 'ross', friends: ['frank']},
];
const withoutFriends = pipe(prop('friends'), isEmpty); // create a function that gets the friends array, and check if it is empty
const withFriends = complement(withoutFriends); // negate the result of withoutFriends
const peopleWithFriends = people.filter(withFriends);
console.log(peopleWithFriends);
const peopleWithoutFriends = people.filter(withoutFriends);
console.log(peopleWithoutFriends);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
Notes:
_.flow()
and R.pipe
execute the sequence from left to right (top to bottom). The functions _.compose()
and R.compose
order is reversed.true
, the item is removed. For example, R.reject(foo, xs)
is equivalent to R.filter(R.complement(foo), xs)
. (noted by @ScottSauyet's in this comment)