Funx.Predicate (funx v0.8.2)
View SourceProvides utility functions for working with predicates—functions that return true or false.
This module enables combining predicates in a declarative way using logical operations.
Combinator Hierarchy
The predicate algebra is built on three primitives:
p_all/1: Combines predicates with AND logic (structural primitive)p_any/1: Combines predicates with OR logic (structural primitive)p_not/1: Negates a predicate
Binary convenience functions are thin wrappers over the primitives:
p_and/2: Binary AND, equivalent top_all([pred1, pred2])p_or/2: Binary OR, equivalent top_any([pred1, pred2])p_none/1: Negated OR, equivalent top_not(p_any(predicates))
The Predicate DSL compiles exclusively to p_all, p_any, and p_not, treating them
as the canonical forms.
Empty List Semantics
The algebra allows empty lists and returns logical identity values:
p_all([])returns a predicate that always returnstrue(AND identity)p_any([])returns a predicate that always returnsfalse(OR identity)p_none([])returns a predicate that always returnstrue(negated OR identity)
The Predicate DSL also supports empty blocks, returning the same identity values:
all do endreturnsfn _ -> true end(AND identity)any do endreturnsfn _ -> false end(OR identity)negate_all do endreturnsfn _ -> false end(NOT true)negate_any do endreturnsfn _ -> true end(NOT false)
Examples
Combining predicates with p_and/2:
iex> is_adult = fn person -> person.age >= 18 end
iex> has_ticket = fn person -> person.tickets > 0 end
iex> can_enter = Funx.Predicate.p_and(is_adult, has_ticket)
iex> can_enter.(%{age: 20, tickets: 1})
true
iex> can_enter.(%{age: 16, tickets: 1})
falseUsing p_or/2 for alternative conditions:
iex> is_vip = fn person -> person.vip end
iex> is_sponsor = fn person -> person.sponsor end
iex> can_access_vip_area = Funx.Predicate.p_or(is_vip, is_sponsor)
iex> can_access_vip_area.(%{vip: true, sponsor: false})
true
iex> can_access_vip_area.(%{vip: false, sponsor: false})
falseNegating predicates with p_not/1:
iex> is_minor = fn person -> person.age < 18 end
iex> is_adult = Funx.Predicate.p_not(is_minor)
iex> is_adult.(%{age: 20})
true
iex> is_adult.(%{age: 16})
falseUsing p_all/1 and p_any/1 for predicate lists:
iex> is_adult = fn person -> person.age >= 18 end
iex> has_ticket = fn person -> person.tickets > 0 end
iex> conditions = [is_adult, has_ticket]
iex> must_meet_all = Funx.Predicate.p_all(conditions)
iex> must_meet_any = Funx.Predicate.p_any(conditions)
iex> must_meet_all.(%{age: 20, tickets: 1})
true
iex> must_meet_all.(%{age: 20, tickets: 0})
false
iex> must_meet_any.(%{age: 20, tickets: 0})
true
iex> must_meet_any.(%{age: 16, tickets: 0})
falseUsing p_none/1 to reject multiple conditions:
iex> is_adult = fn person -> person.age >= 18 end
iex> is_vip = fn person -> person.vip end
iex> cannot_enter = Funx.Predicate.p_none([is_adult, is_vip])
iex> cannot_enter.(%{age: 20, vip: true})
false
iex> cannot_enter.(%{age: 16, vip: false})
true
Summary
Functions
Composes a projection (optic or function) with a predicate.
Combines a list of predicates (p_list) using logical AND.
Returns true only if all predicates return true. An empty list returns true.
Combines two predicates (pred1 and pred2) using logical AND.
Returns a predicate that evaluates to true only if both pred1 and pred2 return true.
Combines a list of predicates (p_list) using logical OR.
Returns true if at least one predicate returns true. An empty list returns false.
Combines a list of predicates (p_list) using logical NOR (negated OR).
Returns true only if none of the predicates return true. An empty list returns true.
Negates a predicate (pred).
Returns a predicate that evaluates to true when pred returns false, and vice versa.
Combines two predicates (pred1 and pred2) using logical OR.
Returns a predicate that evaluates to true if either pred1 or pred2 return true.
Creates a predicate from a block of predicate compositions.
Types
Functions
Composes a projection (optic or function) with a predicate.
This allows checking predicates on projected values (focused parts of data).
Projection Types and Semantics
Lens (Total Projection)
- Semantics: Always focuses on a single value
- Success: Applies predicate to the focused value
- Failure: Raises if field is missing (total projection contract)
- Note: The raising behavior is enforced by the Lens implementation (
lens.view/1), not by this composition function. This function delegates to the Lens contract.
Prism (Partial Projection)
- Semantics: May focus on a value (returns Maybe monad)
- Success: When focus succeeds (Just), applies predicate to unwrapped value
- Failure: When focus fails (Nothing), returns
falsewithout applying predicate - Contract: Missing or nil values return
false, not an error
Traversal (Multi-Focus Projection)
- Semantics: Focuses on zero or more values (returns list of foci)
- Success: Returns
trueif at least one focused value passes the predicate (existential) - Failure: Returns
falseif all focused values fail or if no foci exist - Contract: Uses existential quantification (∃), not universal (∀)
Function (Custom Projection)
- Semantics: Projects value using the provided function
- Success: Applies predicate to the function result
- Failure: No built-in failure mode; function must handle edge cases
Projection Failure Behavior
When a projection fails to focus on a value:
- Prism: Returns
false(graceful degradation) - Traversal (empty foci): Returns
false - Lens: Raises error (total projection contract violation)
- Function: Depends on function implementation
Examples
iex> alias Funx.Optics.Prism
iex> is_adult = fn age -> age >= 18 end
iex> check = Funx.Predicate.compose_projection(Prism.key(:age), is_adult)
iex> check.(%{age: 20})
true
iex> check.(%{age: 16})
false
iex> check.(%{}) # Missing key returns false
false
iex> alias Funx.Optics.Lens
iex> is_long = fn s -> String.length(s) > 5 end
iex> check = Funx.Predicate.compose_projection(Lens.key(:name), is_long)
iex> check.(%{name: "Alexander"})
true
iex> check.(%{name: "Joe"})
false
iex> alias Funx.Optics.Traversal
iex> # Traversal: predicate receives list of foci to relate them
iex> has_high_score = fn scores -> Enum.any?(scores, fn score -> score > 90 end) end
iex> check = Funx.Predicate.compose_projection(
...> Traversal.combine([Lens.key(:score1), Lens.key(:score2)]),
...> has_high_score
...> )
iex> check.(%{score1: 95, score2: 80}) # At least one score > 90
true
iex> check.(%{score1: 80, score2: 85}) # No scores > 90
false
Combines a list of predicates (p_list) using logical AND.
Returns true only if all predicates return true. An empty list returns true.
Examples
iex> is_adult = fn person -> person.age >= 18 end
iex> has_ticket = fn person -> person.tickets > 0 end
iex> can_enter = Funx.Predicate.p_all([is_adult, has_ticket])
iex> can_enter.(%{age: 20, tickets: 1})
true
iex> can_enter.(%{age: 16, tickets: 1})
false
Combines two predicates (pred1 and pred2) using logical AND.
Returns a predicate that evaluates to true only if both pred1 and pred2 return true.
Examples
iex> is_adult = fn person -> person.age >= 18 end
iex> has_ticket = fn person -> person.tickets > 0 end
iex> can_enter = Funx.Predicate.p_and(is_adult, has_ticket)
iex> can_enter.(%{age: 20, tickets: 1})
true
iex> can_enter.(%{age: 16, tickets: 1})
false
Combines a list of predicates (p_list) using logical OR.
Returns true if at least one predicate returns true. An empty list returns false.
Examples
iex> is_vip = fn person -> person.vip end
iex> is_sponsor = fn person -> person.sponsor end
iex> can_access_vip_area = Funx.Predicate.p_any([is_vip, is_sponsor])
iex> can_access_vip_area.(%{vip: true, sponsor: false})
true
iex> can_access_vip_area.(%{vip: false, sponsor: false})
false
Combines a list of predicates (p_list) using logical NOR (negated OR).
Returns true only if none of the predicates return true. An empty list returns true.
Examples
iex> is_adult = fn person -> person.age >= 18 end
iex> is_vip = fn person -> person.vip end
iex> cannot_enter = Funx.Predicate.p_none([is_adult, is_vip])
iex> cannot_enter.(%{age: 20, vip: true})
false
iex> cannot_enter.(%{age: 16, vip: false})
true
Negates a predicate (pred).
Returns a predicate that evaluates to true when pred returns false, and vice versa.
Examples
iex> is_minor = fn person -> person.age < 18 end
iex> is_adult = Funx.Predicate.p_not(is_minor)
iex> is_adult.(%{age: 20})
true
iex> is_adult.(%{age: 16})
false
Combines two predicates (pred1 and pred2) using logical OR.
Returns a predicate that evaluates to true if either pred1 or pred2 return true.
Examples
iex> is_vip = fn person -> person.vip end
iex> is_sponsor = fn person -> person.sponsor end
iex> can_access_vip_area = Funx.Predicate.p_or(is_vip, is_sponsor)
iex> can_access_vip_area.(%{vip: true, sponsor: false})
true
iex> can_access_vip_area.(%{vip: false, sponsor: false})
false
Creates a predicate from a block of predicate compositions.
Returns a function (any() -> boolean()) that can be used with Enum.filter,
Enum.find, and other functions that accept predicates.
Directives
- Bare predicate - Include predicate in composition
negate- Negate the predicatecheck- Compose projection with predicate (check projected value)any- At least one nested predicate must pass (OR logic)all- All nested predicates must pass (AND logic, implicit at top level)
Predicate Forms
The DSL accepts predicates in multiple forms:
Variables (no parentheses needed)
When a predicate is bound to a variable, reference it directly:
is_adult = fn user -> user.age >= 18 end
pred do
is_adult # Variable reference - no () needed
endHelper Functions (parentheses required)
When using 0-arity functions that return predicates, call them with ():
defmodule Helpers do
def adult?, do: fn user -> user.age >= 18 end
end
pred do
Helpers.adult?() # Must call with () to get the predicate
endWhy () is required: The DSL cannot distinguish at compile time between
a function reference and a function call. Using () makes the intent explicit
and ensures the predicate function is retrieved.
Anonymous Functions (inline)
Define predicates inline using fn:
pred do
fn user -> user.age >= 18 end
endCaptured Functions
Use the capture operator & for named functions:
pred do
&adult?/1
endBehaviour Modules
For reusable validation logic, implement Funx.Predicate.Dsl.Behaviour:
defmodule IsActive do
@behaviour Funx.Predicate.Dsl.Behaviour
def pred(_opts), do: fn user -> user.active end
end
pred do
IsActive # Bare module reference
{HasMinimumAge, minimum: 21} # With options
endExamples
use Funx.Predicate
# Simple composition (implicit AND)
pred do
is_adult
has_ticket
end
# With any block (OR logic)
pred do
is_admin
any do
is_vip
is_sponsor
end
end
# With negation
pred do
is_verified
negate is_banned
end
# With projection (check directive)
pred do
is_adult
check :email, fn email -> String.contains?(email, "@") end
end
# With nested field projection (list paths)
pred do
check [:user, :profile, :age], fn age -> age >= 18 end
check [:user, :settings, :notifications], fn n -> n == true end
end
# With negated projection
pred do
is_adult
negate check :banned, fn b -> b == true end
end
# Complex nesting
pred do
any do
all do
is_admin
is_verified
end
all do
is_moderator
has_permission
end
end
negate is_suspended
end