Funx.Ord (funx v0.8.0)
View SourceProvides utilities and DSL for working with the Funx.Ord.Protocol.
This module combines:
- Utility functions for ordering and comparison
- A declarative DSL for building complex orderings
Utility Functions
These functions work with types that support Elixir's comparison operators
or implement the Funx.Ord.Protocol:
contramap/2- Transform ordering by applying a projectioncompare/3- Compare two values, returns:lt,:eq, or:gtmax/3,min/3- Find maximum or minimum of two valuesclamp/4,between/4- Range operationsreverse/1- Reverse ordering logiccomparator/1- Convert to Elixir comparator forEnum.sort/2to_eq/1- Convert to equality comparatorappend/2,concat/1- Combine multiple orderings
DSL
The DSL provides a declarative syntax for building total orderings over complex data structures.
Use use Funx.Ord to import both utilities and DSL:
use Funx.Ord
ord do
asc :name
desc :age
endThe DSL compiles at compile time into efficient compositions using contramap, reverse, and concat,
eliminating the need to manually compose ordering functions.
Directions
:asc- Ascending order (smallest to largest):desc- Descending order (largest to smallest)
Projection Types
- Atom - Field access via
Prism.key(atom). Safe for nil values. - Atom with or_else - Optional field with fallback value
- Function - Direct projection
fn x -> ... endor&fun/1 - Lens - Explicit lens for nested access
- Prism - Explicit prism for optional fields
- Prism with or_else - Optional with fallback
- Behaviour - Custom ordering via
Funx.Ord.Dsl.Behaviour - Ord variable - Existing ord map to compose
See Funx.Ord.Dsl.Behaviour for implementing custom ordering logic.
Summary
Functions
Appends two Ord instances, combining their comparison logic.
Checks if value is between min and max, inclusive, with an optional custom Ord.
Clamps a value between min and max, with an optional custom Ord.
Creates a comparator function from the given Ord module, returning true
if a is less than or equal to b according to the module's ordering.
Compares two values and returns :lt, :eq, or :gt, with an optional custom Ord.
Concatenates a list of Ord instances into a single composite comparator.
Transforms an ordering by applying a projection before comparison.
Returns the maximum of two values, with an optional custom Ord.
Returns the minimum of two values, with an optional custom Ord.
Creates an ordering from a block of projection specifications.
Reverses the ordering logic.
Converts an Ord instance into an equality comparator.
Converts an Ord DSL result or projection to an ord_map.
Types
Functions
Appends two Ord instances, combining their comparison logic.
If the first Ord comparator determines an order, that result is used.
If not, the second comparator is used as a fallback.
Examples
iex> ord1 = Funx.Ord.contramap(& &1.age, Funx.Ord.Protocol.Any)
iex> ord2 = Funx.Ord.contramap(& &1.name, Funx.Ord.Protocol.Any)
iex> combined = Funx.Ord.append(ord1, ord2)
iex> combined.lt?.(%{age: 30, name: "Alice"}, %{age: 30, name: "Bob"})
true
Checks if value is between min and max, inclusive, with an optional custom Ord.
Examples
iex> Funx.Ord.between(5, 1, 10)
true
iex> Funx.Ord.between(0, 1, 10)
false
iex> Funx.Ord.between(11, 1, 10)
false
Clamps a value between min and max, with an optional custom Ord.
Examples
iex> Funx.Ord.clamp(5, 1, 10)
5
iex> Funx.Ord.clamp(0, 1, 10)
1
iex> Funx.Ord.clamp(15, 1, 10)
10
Creates a comparator function from the given Ord module, returning true
if a is less than or equal to b according to the module's ordering.
Useful for sorting with Enum.sort/2 or similar functions.
Examples
iex> comparator = Funx.Ord.comparator(Funx.Ord.Protocol.Any)
iex> Enum.sort([3, 1, 2], comparator)
[1, 2, 3]
Compares two values and returns :lt, :eq, or :gt, with an optional custom Ord.
Examples
iex> Funx.Ord.compare(3, 5)
:lt
iex> Funx.Ord.compare(7, 7)
:eq
iex> Funx.Ord.compare(9, 4)
:gt
Concatenates a list of Ord instances into a single composite comparator.
This function reduces a list of Ord comparators into a single Ord,
applying them in sequence until an order is determined.
Examples
iex> ord_list = [
...> Funx.Ord.contramap(& &1.age, Funx.Ord.Protocol.Any),
...> Funx.Ord.contramap(& &1.name, Funx.Ord.Protocol.Any)
...> ]
iex> combined = Funx.Ord.concat(ord_list)
iex> combined.gt?.(%{age: 25, name: "Charlie"}, %{age: 25, name: "Bob"})
true
@spec contramap( (a -> b) | Funx.Optics.Lens.t() | Funx.Optics.Prism.t() | {Funx.Optics.Prism.t(), b}, ord_t() ) :: ord_map() when a: any(), b: any()
Transforms an ordering by applying a projection before comparison.
Canonical Normalization Layer
This function defines the single normalization point for all projections in the Ord DSL. Every projection type resolves to one of these four forms:
Lens.t()- Usesview!/2to extract the focused value (raises on missing)Prism.t()- Usespreview/2, returnsMaybe, withNothing < Just(_)ordering{Prism.t(), or_else}- Usespreview/2, falling back toor_elseonNothing(a -> b)- Projection function applied directly
All DSL syntax sugar (atoms, helpers, etc.) normalizes to these types in the parser. This function is the only place that converts optics to executable functions.
The ord parameter may be an Ord module or a custom comparator map
with :lt?, :le?, :gt?, and :ge? functions. The projection is applied
to both inputs before invoking the underlying comparator.
Examples
Using a projection function:
iex> ord = Funx.Ord.contramap(&String.length/1)
iex> ord.lt?.("cat", "zebra")
true
iex> ord.gt?.("zebra", "cat")
trueUsing a lens for single key access:
iex> ord = Funx.Ord.contramap(Funx.Optics.Lens.key(:age))
iex> ord.gt?.(%{age: 40}, %{age: 30})
true
iex> ord.lt?.(%{age: 30}, %{age: 40})
trueUsing a bare prism (Nothing < Just):
iex> prism = Funx.Optics.Prism.key(:score)
iex> ord = Funx.Ord.contramap(prism)
iex> ord.lt?.(%{}, %{score: 20})
true
iex> ord.gt?.(%{score: 30}, %{})
trueUsing a prism with an or_else value:
iex> prism = Funx.Optics.Prism.key(:score)
iex> ord = Funx.Ord.contramap({prism, 0})
iex> ord.lt?.(%{score: 10}, %{score: 20})
true
iex> ord.lt?.(%{}, %{score: 20})
true
iex> ord.gt?.(%{score: 30}, %{})
true
Returns the maximum of two values, with an optional custom Ord.
Examples
iex> Funx.Ord.max(3, 5)
5
iex> ord = Funx.Ord.contramap(&String.length/1, Funx.Ord.Protocol.Any)
iex> Funx.Ord.max("cat", "zebra", ord)
"zebra"
Returns the minimum of two values, with an optional custom Ord.
Examples
iex> Funx.Ord.min(10, 7)
7
iex> ord = Funx.Ord.contramap(&String.length/1, Funx.Ord.Protocol.Any)
iex> Funx.Ord.min("apple", "kiwi", ord)
"kiwi"
Creates an ordering from a block of projection specifications.
Returns a %Funx.Monoid.Ord{} struct that can be used with Funx.Ord
functions like compare/3, max/3, min/3, or comparator/1.
Examples
ord do
asc :name
desc :age
end
ord do
asc :score, or_else: 0
desc &String.length(&1.bio)
end
# With nested field paths
ord do
asc [:user, :profile, :created_at]
desc [:user, :stats, :score]
end
Reverses the ordering logic.
Examples
iex> ord = Funx.Ord.reverse(Funx.Ord.Protocol.Any)
iex> ord.lt?.(10, 5)
true
@spec to_eq(ord_t()) :: Funx.Eq.eq_map()
Converts an Ord instance into an equality comparator.
This function creates a map containing two functions:
eq?/2: Returnstrueifaandbare considered equal by the givenOrd.not_eq?/2: Returnstrueifaandbare not considered equal by the givenOrd.
Examples
iex> eq = Funx.Ord.to_eq(Funx.Ord.Protocol.Any)
iex> eq.eq?.(5, 5)
true
Converts an Ord DSL result or projection to an ord_map.
If passed a plain map with lt?/2, le?/2, gt?/2, and ge?/2 functions
(the result of ord do ... end), returns it directly. Otherwise, delegates
to contramap/2.
Used internally by Funx.Macros.ord_for/3 to support both projection-based
and DSL-based ordering definitions.