Current section

Files

Jump to
funx lib eq dsl behaviour.ex
Raw

lib/eq/dsl/behaviour.ex

defmodule Funx.Eq.Dsl.Behaviour do
@moduledoc """
Behaviour for custom equality logic in the Eq DSL.
Implement this behaviour to define reusable Eq comparators that can be
used with `on` directives in the DSL without implementing the Eq protocol.
This is useful for teams that want to avoid teaching developers about protocols,
or want struct-specific equality without global protocol implementations.
## Basic Example
defmodule UserById do
@behaviour Funx.Eq.Dsl.Behaviour
@impl true
def eq(_opts) do
Funx.Eq.contramap(&(&1.id))
end
end
# In DSL
use Funx.Eq
eq do
on UserById # Compares by id
end
## With Options
defmodule UserByName do
@behaviour Funx.Eq.Dsl.Behaviour
@impl true
def eq(opts) do
case_sensitive = Keyword.get(opts, :case_sensitive, true)
if case_sensitive do
Funx.Eq.contramap(&(&1.name))
else
Funx.Eq.contramap(fn u -> String.downcase(u.name) end)
end
end
end
# In DSL
eq do
on UserByName, case_sensitive: false
end
## Why Use This Instead of Protocols?
- **Simpler**: Just one function returning an Eq map
- **No protocol knowledge required**: Easier for team onboarding
- **Module-specific**: Override struct equality without global protocol
- **Options support**: Built-in support for configuration
The returned Eq map typically uses `Funx.Eq.contramap/2` to build
projection-based equality, but can implement any custom comparison logic.
"""
@doc """
Returns an Eq map for comparison.
Takes options and returns an Eq map (with `:eq?` and `:not_eq?` functions).
## Arguments
* `opts` - Keyword list of options passed from the DSL
## Return Value
An Eq map with the structure:
%{
eq?: (any(), any() -> boolean()),
not_eq?: (any(), any() -> boolean())
}
## Examples
# Simple projection-based equality
def eq(_opts) do
Funx.Eq.contramap(&(&1.id))
end
# With options
def eq(opts) do
field = Keyword.get(opts, :field, :id)
Funx.Eq.contramap(&Map.get(&1, field))
end
# Custom comparison logic
def eq(_opts) do
%{
eq?: fn a, b -> normalize(a) == normalize(b) end,
not_eq?: fn a, b -> normalize(a) != normalize(b) end
}
end
Most implementations use `Funx.Eq.contramap/2` for projection-based
equality, which handles the Eq map creation automatically.
"""
@callback eq(opts :: keyword()) :: Funx.Eq.eq_map()
end