Author jfeist
Popularity
1 Star
Updated Last
2 Years Ago
Started In
October 2020

# PredicateComposition

This package defines some functions that do logical composition of predicate functions (i.e., functions that return a boolean). These are higher-order functions, i.e., functions that take functions as arguments and return a new function. Julia implements them efficiently. This permits a compact notation for selecting specific elements out of collections.

## Logical compositions

```AND(f1,f2) = (args...) -> f1(args...) && f2(args...)
OR(f1,f2) = (args...) -> f1(args...) || f2(args...)```

## Numerical comparisons

These functions give functions that compare the output values of different functions. The last three (`MAX`, `MIN`, `SUM`) can take an arbitrary number of arguments, and create functions that (normally) return numerical, not logical, values.

```ISEQUAL(f1,f2) = (args...) -> f1(args...) == f2(args...)
ISLESS(f1,f2) = (args...) -> f1(args...) < f2(args...)
ISLESSEQ(f1,f2) = (args...) -> f1(args...) ≤ f2(args...)
ISGREATER(f1,f2) = (args...) -> f1(args...) > f2(args...)
ISGREATEREQ(f1,f2) = (args...) -> f1(args...) ≥ f2(args...)

MAX(fs...) = (args...) -> max((f(args...) for f in fs)...)
MIN(fs...) = (args...) -> min((f(args...) for f in fs)...)
SUM(fs...) = (args...) -> +((f(args...) for f in fs)...)```

## Aliases

For convenient typing, we also define the following aliases using operators that do not have a predefined meaning in Julia.

• `⩓ = AND` (type with `\And<Tab>`)
• `⩔ = OR` (type with `\Or<Tab>`)
• `≣ = ISEQUAL` (type with `\Equiv<Tab>`)
• `≺ = ISLESS` (type with `\prec<Tab>`)
• `⪯ = ISLESSEQ` (type with `\preceq<Tab>`)
• `≻ = ISGREATER` (type with `\succ<Tab>`)
• `⪰ = ISGREATEREQ` (type with `\succeq<Tab>`)

CAUTION: The operators `⩓` and `⩔`, although they do not have predefined meaning, have the precedence of addition operators, which is higher than comparison operators like `≻` and `≺`. So a statement like `length ≻ 2 ⩓ length ≺ 7` would be parsed as `length ≻ (2 ⩓ length) ≺ 7`, i.e., the function `x -> length(x) > (2(x) && length(x)) < 7(x)`, instead of the probably desired `x -> (length(x) > 2) && (length(x) < 7)`. So to logically compose functions like this, you have to explicitly add parentheses, e.g., `(length ≻ 2) ⩓ (length ≺ 7)`.

For a complete list of operator precedence rules, see https://github.com/JuliaLang/julia/blob/master/src/julia-parser.scm.

## Examples

`length_from_3_to_5 = (length ⪰ 3) ⩓ (length ⪯ 5)`

which is equivalent to

`length_from_3_to_5(x) = length(x) ≥ 3 && length(x) ≤ 5`

The compact notation in particular is advantageous for passing as a function argument to `filter` and similar functions, compare

`filter((length ⪰ 3) ⩓ (length ⪯ 5), collection)`

to

`filter(x -> length(x) ≥ 3 && length(x) ≤ 5, collection)`

Assuming we have functions `count_apples`, `count_oranges`, and `count_lemons` that count the number of apples, oranges, and lemons in some data structure describing a fruit basket, we can do

`filter(MAX(count_apples,count_oranges,count_lemons) ⪰ 3, fruit_baskets)`

`filter(x -> max(count_apples(x),count_oranges(x),count_lemons(x)) ⪰ 3, fruit_baskets)`