Powered by AppSignal & Oban Pro

Funx.Eq

livebooks/eq/eq.livemd

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)