Problem:
Our predicate functions are checking too much and are in charge of more than should.
enum UserRole {
Administrator = 1,
Editor = 2,
Subscriber = 3,
Writer = 4,
}
interface User {
username: string;
age: number;
role: UserRole;
}
const users = [
{ username: "John", age: 25, role: UserRole.Administrator },
{ username: "Jane", age: 7, role: UserRole.Subscriber },
{ username: "Liza", age: 18, role: UserRole.Writer },
{ username: "Jim", age: 16, role: UserRole.Editor },
{ username: "Bill", age: 32, role: UserRole.Editor },
];
const greaterThen17AndWriterOrEditor = users.filter((user: User) => {
return (
user.age > 17 &&
(user.role === UserRole.Writer || user.role === UserRole.Editor)
);
});
const greaterThen5AndSubscriberOrWriter = users.filter((user: User) => {
return user.age > 5 && user.role === UserRole.Writer;
});
Solution:
We have to start using the predicate combinators. It will increase code readability and reusability.
type PredicateFn = (value: any, index?: number) => boolean;
type ProjectionFn = (value: any, index?: number) => any;
function or(...predicates: PredicateFn[]): PredicateFn {
return (value) => predicates.some((predicate) => predicate(value));
}
function and(...predicates: PredicateFn[]): PredicateFn {
return (value) => predicates.every((predicate) => predicate(value));
}
function not(...predicates: PredicateFn[]): PredicateFn {
return (value) => predicates.every((predicate) => !predicate(value));
}
Let's take a look at the combinator predicates in action:
const isWriter = (user: User) => user.role === UserRole.Writer;
const isEditor = (user: User) => user.role === UserRole.Editor;
const isGreaterThan17 = (user: User) => user.age > 17;
const isGreaterThan5 = (user: User) => user.age > 5;
const greaterThan17AndWriterOrEditor = users.filter(
and(isGreaterThan17, or(isWriter, isEditor))
);
const greaterThan5AndSubscriberOrWriter = users.filter(
and(isGreaterThan5, isWriter)
);