Funx.Eq.Dsl.Behaviour behaviour (funx v0.8.0)

View Source

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.

Summary

Callbacks

Returns an Eq map for comparison.

Callbacks

eq(opts)

@callback eq(opts :: keyword()) :: Funx.Eq.eq_map()

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.