Powered by AppSignal & Oban Pro

Funx.Macros

livebooks/macros/macros.livemd

Funx.Macros

Mix.install([
  {:funx, "0.8.8"}
])

Provides macros for automatically implementing Funx.Eq and Funx.Ord protocols for a given struct based on a specified field.

These macros simplify the process of defining equality and ordering behaviors for custom structs by leveraging an existing field's comparison operations.

Function Examples

import Funx.Macros
alias Funx.Eq.Protocol, as: Eq
alias Funx.Ord.Protocol, as: Ord

eq_for/2

Generates an implementation of the Funx.Eq.Protocol for the given struct, using the specified field as the basis for equality comparison.

Examples

defmodule Person do
  defstruct [:name, :age]
end

require Funx.Macros
Funx.Macros.eq_for(Person, :age)
Eq.eq?(%Person{age: 30}, %Person{age: 30})
Eq.eq?(%Person{age: 25}, %Person{age: 30})

ord_for/2

Generates an implementation of the Funx.Ord.Protocol for the given struct, using the specified field as the basis for ordering comparisons.

Examples

defmodule Person do
  defstruct [:name, :age]
end

require Funx.Macros
Funx.Macros.ord_for(Person, :age)
Ord.lt?(%Person{age: 25}, %Person{age: 30})
Ord.gt?(%Person{age: 35}, %Person{age: 30})

Using DSLs with Macros

The macros also accept pre-built comparators from the eq do ... end and ord do ... end DSLs. This allows you to define complex multi-field equality and ordering logic while still getting automatic protocol implementation.

eq_for with Eq DSL

When you need equality based on multiple fields or custom logic, use the Eq DSL:

defmodule User do
  defstruct [:id, :name, :email, :role]
end

use Funx.Eq
require Funx.Macros

# Equal if both name and email match (ignoring id and role)
Funx.Macros.eq_for(User, eq do
  on :name
  on :email
end)
defmodule Employee do
  defstruct [:id, :name, :department]
end

use Funx.Eq
require Funx.Macros

# Two employees are equal if they have the same name and department
Funx.Macros.eq_for(Employee, eq do
  on :name
  on :department
end)
# Same name and department - equal (even with different ids)
Eq.eq?(
  %Employee{id: 1, name: "Alice", department: "Engineering"},
  %Employee{id: 2, name: "Alice", department: "Engineering"}
)
# Different department - not equal
Eq.eq?(
  %Employee{id: 1, name: "Alice", department: "Engineering"},
  %Employee{id: 2, name: "Alice", department: "Sales"}
)

ord_for with Ord DSL

When you need ordering based on multiple fields with different directions, use the Ord DSL:

defmodule Product do
  defstruct [:name, :price, :rating]
end

use Funx.Ord
require Funx.Macros

# Sort by rating (descending), then by name (ascending)
Funx.Macros.ord_for(Product, ord do
  desc :rating
  asc :name
end)
defmodule Article do
  defstruct [:title, :published_at, :views]
end

use Funx.Ord
require Funx.Macros

# Sort articles by views (descending), then by title (ascending)
Funx.Macros.ord_for(Article, ord do
  desc :views
  asc :title
end)
# Higher views comes first
Ord.lt?(
  %Article{title: "First Post", views: 1000},
  %Article{title: "Second Post", views: 500}
)
# Same views - sort by title alphabetically
Ord.lt?(
  %Article{title: "Alpha", views: 100},
  %Article{title: "Beta", views: 100}
)
# Sort a list of articles
articles = [
  %Article{title: "Zebra", views: 100},
  %Article{title: "Alpha", views: 500},
  %Article{title: "Beta", views: 500},
  %Article{title: "Gamma", views: 100}
]

Enum.sort(articles, &Ord.le?/2)

When to Use DSLs vs Simple Projections

Use a simple projection (atom, lens, function) when:

  • Equality/ordering is based on a single field
  • You need straightforward comparison

Use the DSL when:

  • You need multi-field comparison
  • Fields should be compared in a specific order (for ordering)
  • You need mixed ascending/descending directions
  • You want to leverage DSL features like or_else for nil handling