# Elly style

Stylistic suggestions for writing Elly by hand or generating it (examples, tests,
benchmarks). A living document — add to it as conventions settle. These are
*preferences*, not spec rules; the grammar is in `docs/elly-spec.md` and the Muon
layer it sits on is in `docs/muon-spec.md`.

## Prefer the spread-call form

Write `f(a, b, c)` rather than the bare application spine `f a b c`. Inside a `(…)`
group commas and newlines are interchangeable, and each comma/newline-separated
segment is a full sub-expression *spread* as one argument to whatever precedes the
group — so `f(a, b)` is exactly `f a b`. The spread form lets a call wrap across
lines while staying one chain:

```elly
__Int_for(0, n,
  0,
  &(i, acc) __Int_add acc (f i))
```

Mixing the two forms in one program is fine and idiomatic — reach for the space
form when a call is short (`__List_get al i`) and the spread form when arguments
are long or want their own lines (`__eq(k, target, &_ hit, &_ acc)`).

Spread composes: `__Int_add(__Int_mul(i, n), k)` reads as `__Int_add (__Int_mul i n) k`,
because each segment is reduced independently before being spread.

A `[…]` list literal is *already* a multi-line-friendly scope, so pass one
directly — `__match [c0, c1, …]`, not `__match([c0, c1, …])`. The list's own
brackets carry the newlines; wrapping it in a `(…)` spread just to reach one
argument is redundant.

## Prefer binder groups for multiple parameters

Write `&(i, acc)` rather than `&i &acc`. They desugar identically (a binder group
curries left to right), but the group reads as a parameter list. Patterns work in
either position: `&(i, [seed, acc])` binds `i` and destructures the second argument.

Keep `&x &y` (two separate `&`) only when the two-stage currying is the point — e.g.
a fixpoint `Z (&rec &n …)`, where `rec` is supplied first and the result is applied
to `n`.

## Prefer the pipe for a subject-last builtin

Write `val |> __match [clauses]` rather than `__match [clauses] val`. The pipe
`L |> R` lowers to `App(R, L)`, threading the left operand in as `R`'s final
argument — so it is the same call, read subject-first:

```elly
n |> __match [
  &(< 2) n,
  &_ __Int_add (rec (__Int_sub n 1)) (rec (__Int_sub n 2))
]
```

`|>` binds looser than application but tighter than `&`/`let`, so it resolves
within a split-free segment and needs no extra parentheses in the common case.

## Mind the one-chain layout rule

A program (and each `let` body) is a single chain, so newlines are load-bearing:

- A **top-level** newline splits the program into multiple chains
  (`MultipleExpressions`).
- A newline between a `let` binder group and its body orphans the body
  (`LetMissingBody`) — start the body on the group's closing-paren line:
  `) __Int_add a b`.
- A newline is safe *only* inside a `(…)` / `[…]` / `{…}` / `let(…)` scope. Even
  there, a newline that splits a single abstraction body makes the `&`-header its
  own chain (`AbsWithoutBody`): in `(&i &acc <body>)` the whole `&i &acc <body>`
  must be one segment.

So lay multi-line code out as **spread arguments** or **`let` bindings** (both
tolerate newlines between their segments), not by breaking an abstraction body
across lines. When a callback body is genuinely large, give it a name in a `let`:

```elly
let (
  step = &(i, [s, acc]) let (
    s2 = lcg s,
    v  = (__Int_divrem s2 M).1
  ) [s2, (__Int_add acc v)]
) (__Int_for(0, n, [7, 0], step)).1
```

**Stopgap for top-level newlines.** A whole program that wants blank lines or
top-level comments can be wrapped in one `(…)` grouping — a one-element group
tolerates stray newlines and comment-only chains at its top level:

```elly
(
  // a comment, and blank lines, are fine at the top of a `(…)`
  let (x = 1, y = 2) __Int_add x y
)
```

This is only a stopgap: a proper file will be tuple-like with free top-level
newlines once modules are designed. The `(…)` wrapper also happens to sit nicely
inside a Rust multiline string literal (leading/trailing newlines are harmless).

## Projection and modulo idioms

- Positional projection is `list .N`: `pair.0`, `(__Int_divrem a b).1`. The `.N`
  binds to the value on its left; wrap a call whose result you project,
  `(__Int_for(…)).0`.
- There is no bare modulo operator: `x mod m` is `(__Int_divrem x m).1` (the
  remainder is element 1 of the `[quotient, remainder]` pair).

## Symbols and keys

- Symbols are literal-only in the current subset (no runtime symbol construction),
  so a symbol that varies must be written into the source. Map keys, by contrast,
  can be computed at runtime (any value is a key).
- A bare non-digit atom in key position is the symbol of that spelling — `{ k: 1 }`
  is `{ .k: 1 }`. Write `.5` (symbol) or `(5)` (integer key) to disambiguate a
  digit-leading key.
