FORMATTER_EXPORT.md

# Formatter Rules

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:

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

### Step 2: Update .formatter.exs

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

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

## Examples

### Either DSL

With this configuration, your DSL code will format cleanly:

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

Instead of:

```elixir
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:

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

Instead of:

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

### Ord DSL

Your ordering definitions will format cleanly:

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

Instead of:

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

### Predicate DSL

Your predicate definitions will format cleanly:

```elixir
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:

```elixir
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:

```bash
mix format --check-formatted
```

Your DSL code should format without adding parentheses.