Funx.Eq
Section
Mix.install([
{:funx, "0.8.8"}
])
The Funx.Eq module provides utilities for working with equality comparisons.
It combines protocol-based equality (via Funx.Eq.Protocol) with powerful utility
functions for composing and transforming equality checks.
import Funx.Eq
Protocol Basics
The Funx.Eq.Protocol defines an equality function, eq?/2, for comparing two values,
and its complement, not_eq?/2, for checking inequality.
Types that implement this protocol can define custom equality logic, allowing for domain-specific comparisons.
Fallback Behavior
The protocol uses @fallback_to_any true, meaning that if a specific type does not
implement Funx.Eq.Protocol, it falls back to the default implementation for Any, which
uses Elixir’s built-in equality operator (==).
Protocol Examples
With a custom implementation for a Funx.Monad.Maybe type:
eq?(Funx.Monad.Maybe.just(3), Funx.Monad.Maybe.just(3))
eq?(Funx.Monad.Maybe.just(3), Funx.Monad.Maybe.just(5))
eq?(Funx.Monad.Maybe.nothing(), Funx.Monad.Maybe.nothing())
eq?(Funx.Monad.Maybe.nothing(), Funx.Monad.Maybe.just(5))
Checking inequality with not_eq?/2:
not_eq?(Funx.Monad.Maybe.just(3), Funx.Monad.Maybe.just(3))
not_eq?(Funx.Monad.Maybe.just(3), Funx.Monad.Maybe.just(5))
not_eq?(Funx.Monad.Maybe.nothing(), Funx.Monad.Maybe.nothing())
not_eq?(Funx.Monad.Maybe.nothing(), Funx.Monad.Maybe.just(5))
Utility Functions
eq?/3
Returns true if two values are equal, using a specified or default Eq.
eq?(42, 42)
eq?("foo", "bar")
not_eq?/3
Returns false if two values are not equal, using a specified or default Eq.
not_eq?(42, 99)
not_eq?("foo", "foo")
contramap/2
Transforms an equality check by applying a function f to values before comparison.
The eq parameter can be an Eq module or a custom comparator map with an :eq? function.
If an Eq module is provided, it wraps the module’s function to apply f to each value before invoking the equality check.
If a custom comparator map is provided, it wraps the function in the map to apply f to each value.
eq = contramap(& &1.age)
eq.eq?.(%{age: 30}, %{age: 30})
eq = contramap(& &1.age)
eq.eq?.(%{age: 30}, %{age: 25})
eq_by?/4
Checks equality of values by applying a projection function, using a specified or default Eq.
The eq parameter can be an Eq module or a custom comparator map with an :eq? function.
eq_by?(& &1.age, %{age: 30}, %{age: 30})
eq_by?(& &1.age, %{age: 30}, %{age: 25})
Monoid Operations
compose_all/2
Composes two equality comparators using the Eq.All monoid.
This function merges two equality comparisons, requiring both to return true
for the final result to be considered equal. This enforces a strict equality rule,
where all comparators must agree.
eq1 = contramap(& &1.name)
eq2 = contramap(& &1.age)
combined = compose_all(eq1, eq2)
eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 30}, combined)
eq1 = contramap(& &1.name)
eq2 = contramap(& &1.age)
combined = compose_all(eq1, eq2)
eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined)
compose_any/2
Composes two equality comparators using the Eq.Any monoid.
This function merges two equality comparisons, where at least one
must return true for the final result to be considered equal.
eq1 = contramap(& &1.name)
eq2 = contramap(& &1.age)
combined = compose_any(eq1, eq2)
eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined)
eq1 = contramap(& &1.name)
eq2 = contramap(& &1.age)
combined = compose_any(eq1, eq2)
eq?(%{name: "Alice", age: 30}, %{name: "Bob", age: 25}, combined)
compose_all/1
Composes a list of equality comparators using the Eq.All monoid.
The resulting comparator requires all comparators in the list to agree that two values are equal.
eq1 = contramap(& &1.name)
eq2 = contramap(& &1.age)
combined = compose_all([eq1, eq2])
eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 30}, combined)
eq1 = contramap(& &1.name)
eq2 = contramap(& &1.age)
combined = compose_all([eq1, eq2])
eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined)
compose_any/1
Composes a list of equality comparators using the Eq.Any monoid.
The resulting comparator allows any comparator in the list to determine equality, making it more permissive.
eq1 = contramap(& &1.name)
eq2 = contramap(& &1.age)
combined = compose_any([eq1, eq2])
eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined)
eq1 = contramap(& &1.name)
eq2 = contramap(& &1.age)
combined = compose_any([eq1, eq2])
eq?(%{name: "Alice", age: 30}, %{name: "Bob", age: 25}, combined)
to_predicate/2
Converts an Eq comparator into a single-argument predicate function for use in Enum functions.
The resulting predicate takes a single element and returns true if it matches the target
based on the specified Eq. If no custom Eq is provided, it defaults to Funx.Eq.Protocol.
eq = contramap(& &1.name)
predicate = to_predicate(%{name: "Alice"}, eq)
Funx.Filterable.filter([%{name: "Alice"}, %{name: "Bob"}], predicate)