Derek Enos

Posted on

# Predilection for Predicates

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` and `OR` on first `false` or `true` 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) }`.