Predilection for Predicates
Derek Enos
Posted on September 8, 2021
I recently wanted to query arrays of Javascript objects using a declarative syntax and happily took the opportunity to write some small functions.
Predicates
EQ (is equal to)
const EQ = x => y => x === y
For anyone unfamiliar with ES6 arrow functions:
EQ
is a function that accepts one argument (x
) that returns another function that accepts one argument (y
) which returns the result of evaluating x === y
.
Here's the equivalent standard function
def:
function EQ (x) {
return function (y) {
return x === y
}
}
IN (is included in)
const IN = (...xs) => x => xs.includes(x)
IN
is a function that accepts one or more arguments collected into an array (xs
) that returns another function that accepts one argument (x
) which returns the result of evaluating xs.includes(x)
.
Logical Operators
const NOT = pred => x => !pred(x)
const AND = (...preds) => x => preds.reduce((acc, pred) => acc && pred(x), true)
const OR = (...preds) => x => preds.reduce((acc, pred) => acc || pred(x), false)
Doing Stuff
Filtering Scalar Arrays
const data = [ 1, 2, 1, 1, 3, 2, 2, 2 ]
Get All the 1
s
>> data.filter(EQ(1))
Array(3) [ 1, 1, 1 ]
Get All the 1
s and 2
s
>> data.filter(IN(1, 2))
Array(7) [ 1, 2, 1, 1, 2, 2, 2 ]
>> data.filter(OR(EQ(1), EQ(2)))
Array(7) [ 1, 2, 1, 1, 2, 2, 2 ]
Filtering Arrays of Objects
The above EQ
and IN
predicate functions work great with scalar values (i.e. numbers, booleans, etc), but I needed something that works on objects:
const OBJ = spec => obj => Object.entries(spec).reduce((acc, [k, pred]) => acc && pred(obj[k]), true)
OBJ
accepts an object-type spec
argument that maps key names to predicates.
For example, a spec
value of:
{ isAdmin: EQ(true), active: EQ(true) }
would match objects with isAdmin = true
AND active = true
. For performing logical ops other than AND
, you can specify them separately and wrap them appropriately. For example, to do an OR
query on these same property values:
OR( OBJ({ isAdmin: EQ(true) }), OBJ({ active: EQ(true) }) )
Better may be to create an OBJ_OR
or something but...moving on
Get some legit-looking data from JSONPlaceholder
const Todos = await (await fetch("https://jsonplaceholder.typicode.com/todos")).json()
The returned array looks like:
[
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
},
...
]
Find all uncompleted Todos from Users 1
and 2
:
>> Todos.filter(OBJ({userId: IN(1, 2), completed: EQ(false)}))
Array(21) [ {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, … ]
🎉
Optimizations omitted in favor of simplicity
Short-circuit
AND
andOR
on firstfalse
ortrue
respectively instead of iterating over the entire array of object entries.Support implicit
EQ
for non-predicate-function object spec values, e.g.{ isAdmin: true }
would be interpreted as{ isAdmin: EQ(true) }
.
Posted on September 8, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 14, 2024
November 10, 2024