PredicateComposition

Stable Dev Build Status Coverage

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.