Current section
Files
Jump to
Current section
Files
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 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 - bare usage (preferred)
use Funx.Eq
eq do
UserById # Compares by id
end
# Or with `on` directive
eq do
on UserById
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 - bare with options
eq do
{UserByName, case_sensitive: false}
end
# Or with `on` directive
eq do
on UserByName, case_sensitive: false
end
## Composing Multiple Behaviours
Behaviour modules can be composed with other eq expressions:
eq do
UserById # bare behaviour
{UserByName, case_sensitive: false} # bare with options
on :email # projection
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