Formatter Rules

View Source

The Funx library exports formatter rules for its DSLs, allowing projects that depend on Funx to automatically format DSL code without extra parentheses.

Exported Rules

Either DSL

The following Either DSL functions are configured to format without parentheses:

  • either/2 - DSL entry point
  • bind/1 - Chain operations that return Either or result tuples
  • map/1 - Transform values with plain functions
  • ap/1 - Apply function in Either to value in Either
  • validate/1 - Collect all errors from validators
  • filter_or_else/2 - Filter with predicate, fallback if fails
  • or_else/1 - Provide fallback on error
  • map_left/1 - Transform error values
  • tap - Run a side-effecting function inside the chain without changing the data

Note that flip/0 - Swap Left and Right still requires parentheses.

Maybe DSL

The following Maybe DSL functions are configured to format without parentheses:

  • maybe/2 - DSL entry point
  • bind/1 - Chain operations that return Maybe, Either, result tuples, or nil (shared with Either DSL)
  • map/1 - Transform values with plain functions (shared with Either DSL)
  • ap/1 - Apply function in Maybe to value in Maybe (shared with Either DSL)
  • or_else/1 - Provide fallback on Nothing (shared with Either DSL)
  • tap/1 - Run a side-effecting function inside the chain without changing the data (shared with Either DSL)
  • filter/1 - Filter with a predicate, returns Nothing if predicate fails
  • filter_map/2 - Filter and transform in one step
  • guard/1 - Guard with a boolean condition

Ord DSL

The following Ord DSL functions are configured to format without parentheses:

  • asc/1 - Ascending order for a projection
  • asc/2 - Ascending order with options (e.g., default:)
  • desc/1 - Descending order for a projection
  • desc/2 - Descending order with options (e.g., default:)

Eq DSL

The following Eq DSL functions are configured to format without parentheses:

  • on/1 - Compare on a projection
  • on/2 - Compare on a projection with options
  • not_on/1 - Exclude a projection from comparison
  • not_on/2 - Exclude a projection from comparison with options
  • any/1 - Match any of the given comparisons (shared with Predicate DSL)
  • all/1 - Match all of the given comparisons (shared with Predicate DSL)

Predicate DSL

The following Predicate DSL functions are configured to format without parentheses:

  • pred/1 - DSL entry point for defining predicates
  • check/2 - Project and test a value (e.g., check :field, predicate)
  • negate/1 - Negate a predicate or block
  • negate_all/1 - Negate an AND block (applies De Morgan's Laws)
  • negate_any/1 - Negate an OR block (applies De Morgan's Laws)
  • any/1 - OR logic - at least one predicate must pass (shared with Eq DSL)
  • all/1 - AND logic - all predicates must pass (shared with Eq DSL)

Usage in Dependent Projects

Step 1: Add to Dependencies

Make sure your mix.exs includes Funx as a dependency:

def deps do
  [
    {:funx, "~> 0.2"}
  ]
end

Step 2: Update .formatter.exs

In your project's .formatter.exs, add :funx to import_deps:

[
  import_deps: [:funx],
  inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

Examples

Either DSL

With this configuration, your DSL code will format cleanly:

either user_input do
  bind ParseUser
  map ValidateEmail
  validate [CheckLength, CheckFormat]
  bind SaveToDatabase
  or_else default_user()
end

Instead of:

either(user_input) do
  bind(ParseUser)
  map(ValidateEmail)
  validate([CheckLength, CheckFormat])
  bind(SaveToDatabase)
  or_else(default_user())
end

Maybe DSL

Your Maybe pipelines will format cleanly:

maybe user_input do
  bind ParseInt
  filter PositiveNumber
  map Double
  or_else default_value()
end

Instead of:

maybe(user_input) do
  bind(ParseInt)
  filter(PositiveNumber)
  map(Double)
  or_else(default_value())
end

Ord DSL

Your ordering definitions will format cleanly:

ord do
  asc :name
  desc :age
  asc :score, default: 0
end

Instead of:

ord do
  asc(:name)
  desc(:age)
  asc(:score, default: 0)
end

Predicate DSL

Your predicate definitions will format cleanly:

pred do
  check :age, fn age -> age >= 18 end
  negate check :banned, fn b -> b == true end
  any do
    check :role, fn r -> r == :admin end
    check :verified, fn v -> v == true end
  end
  negate_all do
    check :suspended, fn s -> s == true end
    check :deleted, fn d -> d == true end
  end
end

Instead of:

pred do
  check(:age, fn age -> age >= 18 end)
  negate(check(:banned, fn b -> b == true end))
  any do
    check(:role, fn r -> r == :admin end)
    check(:verified, fn v -> v == true end)
  end
  negate_all do
    check(:suspended, fn s -> s == true end)
    check(:deleted, fn d -> d == true end)
  end
end

Verification

To verify the formatter rules are being imported correctly, you can run:

mix format --check-formatted

Your DSL code should format without adding parentheses.