# 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)`

instead of

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

to get all fruit baskets with at least three apples, three oranges, or three lemons.