Funx.Eq (funx v0.8.0)
View SourceUtilities and DSL for working with the Funx.Eq.Protocol.
This module provides two main capabilities:
Utility functions for working with equality comparisons:
contramap/2- Transform equality checks via projectionseq?/3,not_eq?/3- Direct equality checksappend_all/2,append_any/2- Combine comparatorsconcat_all/1,concat_any/1- Combine lists of comparatorsto_predicate/2- Convert to single-argument predicates
Declarative DSL for building complex equality comparators:
eq do ... end- Build comparators with clean syntax- Supports
on,diff_on,any, andalldirectives - Compiles at compile-time for efficiency
These functions assume that types passed in either support Elixir's equality operator
or implement the Funx.Eq.Protocol protocol.
DSL Usage
use Funx.Eq
eq do
on :name
on :age
endUtility Usage
Funx.Eq.contramap(&(&1.age))
Funx.Eq.eq?(value1, value2)For detailed DSL documentation, see the eq/1 macro below.
Summary
Functions
Combines two equality comparators using the Eq.All monoid.
Combines two equality comparators using the Eq.Any monoid.
Concatenates a list of equality comparators using the Eq.All monoid.
Concatenates a list of equality comparators using the Eq.Any monoid.
Transforms an equality check by applying a projection before comparison.
Creates an equality comparator from a block of projection specifications.
Returns true if two values are equal, using a specified or default Eq.
Checks equality of two values by applying a projection before comparison.
Returns false if two values are not equal, using a specified or default Eq.
Converts an Eq DSL result or projection to an eq_map.
Converts an Eq comparator into a single-argument predicate function for use in Enum functions.
Types
Functions
Combines 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.
Examples
iex> eq1 = Funx.Eq.contramap(& &1.name)
iex> eq2 = Funx.Eq.contramap(& &1.age)
iex> combined = Funx.Eq.append_all(eq1, eq2)
iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 30}, combined)
true
iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined)
false
Combines 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.
Examples
iex> eq1 = Funx.Eq.contramap(& &1.name)
iex> eq2 = Funx.Eq.contramap(& &1.age)
iex> combined = Funx.Eq.append_any(eq1, eq2)
iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined)
true
iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Bob", age: 25}, combined)
false
Concatenates 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.
Examples
iex> eq1 = Funx.Eq.contramap(& &1.name)
iex> eq2 = Funx.Eq.contramap(& &1.age)
iex> combined = Funx.Eq.concat_all([eq1, eq2])
iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 30}, combined)
true
iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined)
false
Concatenates 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.
Examples
iex> eq1 = Funx.Eq.contramap(& &1.name)
iex> eq2 = Funx.Eq.contramap(& &1.age)
iex> combined = Funx.Eq.concat_any([eq1, eq2])
iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined)
true
iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Bob", age: 25}, combined)
false
@spec contramap( (a -> b) | Funx.Optics.Lens.t() | Funx.Optics.Prism.t() | {Funx.Optics.Prism.t(), b} | Funx.Optics.Traversal.t(), eq_t() ) :: eq_map() when a: any(), b: any()
Transforms an equality check by applying a projection before comparison.
The projection must be one of:
- a function
(a -> b)- Applied directly to extract the comparison value - a
Lens- Usesview!/2to extract the focused value (raises on missing) - a
Prism- Usespreview/2(Nothing == Nothing) - a tuple
{Prism, default}- Usespreview/2, falling back todefaultonNothing - a
Traversal- Usesto_list_maybe/2, compares all foci element-by-element (both must have all foci)
The eq parameter may be an Eq module or a custom comparator map
with :eq? and :not_eq? functions. The projection is applied to both
inputs before invoking the underlying comparator.
Examples
Using a projection function:
iex> eq = Funx.Eq.contramap(& &1.age)
iex> eq.eq?.(%{age: 30}, %{age: 30})
true
iex> eq.eq?.(%{age: 30}, %{age: 25})
falseUsing a lens for single key access:
iex> eq = Funx.Eq.contramap(Funx.Optics.Lens.key(:age))
iex> eq.eq?.(%{age: 40}, %{age: 40})
trueUsing a prism with a default value:
iex> prism = Funx.Optics.Prism.key(:score)
iex> eq = Funx.Eq.contramap({prism, 0})
iex> eq.eq?.(%{score: 10}, %{score: 10})
true
iex> eq.eq?.(%{}, %{score: 0})
true
Creates an equality comparator from a block of projection specifications.
Returns a %Funx.Monoid.Eq.All{} struct that can be used with Funx.Eq
functions like eq?/3, not_eq?/3, or to_predicate/2.
Directives
on- Field/projection must be equaldiff_on- Field/projection must be differentany- At least one nested check must pass (OR logic)all- All nested checks must pass (AND logic, implicit at top level)
Projection Types
The DSL supports the same projection forms as Ord DSL:
- Atom - Field access via
Prism.key(atom) - Atom with or_else - Optional field via
{Prism.key(atom), or_else} - Function - Direct projection
fn x -> ... endor&fun/1 - Lens - Explicit lens for nested access (raises on missing)
- Prism - Explicit prism for optional fields
- Prism with or_else -
{Prism.t(), or_else}for optional with fallback - Behaviour - Custom equality via
Funx.Eq.Dsl.Behaviour.eq/1
Equivalence Relations and diff_on
Core Eq (using only on, all, any) forms an equivalence relation with three properties:
- Reflexive:
eq?(a, a)is always true - Symmetric: If
eq?(a, b)theneq?(b, a) - Transitive: If
eq?(a, b)andeq?(b, c)theneq?(a, c)
These properties guarantee that Core Eq partitions values into equivalence classes, making it safe for use with Enum.uniq/2, MapSet, and grouping operations.
Extended Eq (using diff_on) expresses boolean equality predicates and does not guarantee transitivity.
Important: If you need equivalence classes (grouping, uniq, set membership), do not use diff_on.
Examples
Basic multi-field equality:
use Funx.Eq
eq_person = eq do
on :name
on :age
endUsing diff_on to check difference:
eq_same_person = eq do
on :name
on :email
diff_on :id
endNested any blocks (OR logic):
eq_contact = eq do
any do
on :email
on :username
end
endMixed composition:
eq_mixed = eq do
on :department
any do
on :email
on :username
end
endWith nested field paths:
eq_nested = eq do
on [:user, :profile, :name]
on [:user, :profile, :age]
end
Returns true if two values are equal, using a specified or default Eq.
This function compares the values directly, without applying any projection.
For comparisons that require projecting or focusing on part of a structure,
use Funx.Eq.eq_by?/4 or Funx.Eq.contramap/2.
Examples
iex> Funx.Eq.eq?(42, 42)
true
iex> Funx.Eq.eq?("foo", "bar")
false
@spec eq_by?( (a -> b) | Funx.Optics.Lens.t() | {Funx.Optics.Prism.t(), b}, a, a, eq_t() ) :: boolean() when a: any(), b: any()
Checks equality of two values by applying a projection before comparison.
The projection must be one of:
- a function
(a -> b)- Applied directly to extract the comparison value - a
Lens- Usesview!/2to extract the focused value (raises on missing) - a tuple
{Prism, default}- Usespreview/2, falling back todefaultonNothing
The eq parameter may be an Eq module or a custom comparator map.
The projection is applied to both arguments before invoking the comparator.
Examples
Using a projection function:
iex> Funx.Eq.eq_by?(& &1.age, %{age: 30}, %{age: 30})
true
iex> Funx.Eq.eq_by?(& &1.age, %{age: 30}, %{age: 25})
falseUsing a lens for single key access:
iex> Funx.Eq.eq_by?(Funx.Optics.Lens.key(:age), %{age: 40}, %{age: 40})
trueUsing a prism with a default value:
iex> prism = Funx.Optics.Prism.key(:score)
iex> Funx.Eq.eq_by?({prism, 0}, %{score: 10}, %{score: 10})
true
iex> Funx.Eq.eq_by?({prism, 0}, %{}, %{score: 0})
true
Returns false if two values are not equal, using a specified or default Eq.
This function compares the values directly, without applying any projection.
For comparisons based on a projection, lens, key, or path,
use Funx.Eq.eq_by?/4 or a comparator produced by Funx.Eq.contramap/2.
Examples
iex> Funx.Eq.not_eq?(42, 99)
true
iex> Funx.Eq.not_eq?("foo", "foo")
false
Converts an Eq DSL result or projection to an eq_map.
If passed a plain map with eq?/2 and not_eq?/2 functions (the result
of eq do ... end), returns it directly. Otherwise, delegates to contramap/2.
Used internally by Funx.Macros.eq_for/3 to support both projection-based
and DSL-based equality definitions.
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.
Examples
iex> eq = Funx.Eq.contramap(& &1.name)
iex> predicate = Funx.Eq.to_predicate(%{name: "Alice"}, eq)
iex> Funx.Filterable.filter([%{name: "Alice"}, %{name: "Bob"}], predicate)
[%{name: "Alice"}]