Powered by AppSignal & Oban Pro

Funx.Ord

livebooks/ord/ord.livemd

Funx.Ord

Section

Mix.install([
  {:funx, "0.8.8"}
])

The Funx.Ord module provides utilities for working with ordering comparisons. It combines protocol-based ordering (via Funx.Ord.Protocol) with powerful utility functions for composing and transforming orderings.

import Funx.Ord

Protocol Basics

The Funx.Ord.Protocol defines a set of comparison functions: lt?/2, le?/2, gt?/2, and ge?/2.

This protocol is intended for types that can be ordered, allowing values to be compared for their relative positions in a total order.

By implementing this protocol, you can provide custom logic for how values of a certain type are compared.

Fallback Behavior

The protocol uses @fallback_to_any true, which means if a specific type does not implement Funx.Ord.Protocol, the default implementation for Any will be used, which relies on Elixir's built-in comparison operators.

Protocol Examples

With a custom implementation for a Funx.Monad.Maybe type:

Funx.Ord.Protocol.lt?(Funx.Monad.Maybe.just(3), Funx.Monad.Maybe.just(5))
Funx.Ord.Protocol.lt?(Funx.Monad.Maybe.just(5), Funx.Monad.Maybe.just(3))
Funx.Ord.Protocol.lt?(Funx.Monad.Maybe.nothing(), Funx.Monad.Maybe.just(3))
Funx.Ord.Protocol.le?(Funx.Monad.Maybe.just(3), Funx.Monad.Maybe.just(5))
Funx.Ord.Protocol.le?(Funx.Monad.Maybe.just(5), Funx.Monad.Maybe.just(5))
Funx.Ord.Protocol.le?(Funx.Monad.Maybe.just(5), Funx.Monad.Maybe.just(3))
Funx.Ord.Protocol.gt?(Funx.Monad.Maybe.just(5), Funx.Monad.Maybe.just(3))
Funx.Ord.Protocol.gt?(Funx.Monad.Maybe.just(3), Funx.Monad.Maybe.just(5))
Funx.Ord.Protocol.gt?(Funx.Monad.Maybe.just(3), Funx.Monad.Maybe.nothing())
Funx.Ord.Protocol.ge?(Funx.Monad.Maybe.just(5), Funx.Monad.Maybe.just(3))
Funx.Ord.Protocol.ge?(Funx.Monad.Maybe.just(5), Funx.Monad.Maybe.just(5))
Funx.Ord.Protocol.ge?(Funx.Monad.Maybe.just(3), Funx.Monad.Maybe.just(5))

Utility Functions

These functions assume that types passed in either support Elixir's comparison operators or implement the Funx.Ord.Protocol.

contramap/2

Transforms an ordering by applying a function f to values before comparison.

The ord parameter can be an Ord module or a custom comparator map with comparison functions (:lt?, :le?, :gt?, and :ge?). When an Ord module is provided, it wraps the module's functions to apply f to each value before invoking the comparison. If a custom comparator map is provided, it wraps the functions in the map to apply f to each value.

ord = contramap(&String.length/1, Funx.Ord.Protocol)
ord.lt?.("cat", "zebra")

max/3

Returns the maximum of two values, with an optional custom Ord.

import Kernel, except: [max: 2, min: 2]

max(3, 5)
ord = contramap(&String.length/1, Funx.Ord.Protocol)
max("cat", "zebra", ord)

min/3

Returns the minimum of two values, with an optional custom Ord.

min(10, 7)
ord = contramap(&String.length/1, Funx.Ord.Protocol)
min("apple", "kiwi", ord)

clamp/4

Clamps a value between min and max, with an optional custom Ord.

clamp(5, 1, 10)
clamp(0, 1, 10)
clamp(15, 1, 10)

between/4

Checks if value is between min and max, inclusive, with an optional custom Ord.

between(5, 1, 10)
between(0, 1, 10)
between(11, 1, 10)

compare/3

Compares two values and returns :lt, :eq, or :gt, with an optional custom Ord.

compare(3, 5)
compare(7, 7)
compare(9, 4)

reverse/1

Reverses the ordering logic.

ord = reverse(Funx.Ord.Protocol)
ord.lt?.(10, 5)

comparator/1

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.

comparator_fn = comparator(Funx.Ord.Protocol)
Enum.sort([3, 1, 2], comparator_fn)

to_eq/1

Converts an Ord instance into an equality comparator.

This function creates a map containing two functions:

  • eq?/2: Returns true if a and b are considered equal by the given Ord.
  • not_eq?/2: Returns true if a and b are not considered equal by the given Ord.
eq = to_eq(Funx.Ord.Protocol)
eq.eq?.(5, 5)

compose/2

Composes 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.

ord1 = contramap(& &1.age, Funx.Ord.Protocol)
ord2 = contramap(& &1.name, Funx.Ord.Protocol)
combined = compose(ord1, ord2)
combined.lt?.(%{age: 30, name: "Alice"}, %{age: 30, name: "Bob"})

compose/1

Composes 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.

ord_list = [
  contramap(& &1.age, Funx.Ord.Protocol),
  contramap(& &1.name, Funx.Ord.Protocol)
]
combined = compose(ord_list)
combined.gt?.(%{age: 25, name: "Charlie"}, %{age: 25, name: "Bob"})