# 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-form application: `f(a, b)`, not `f a b`, but use judgement

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))
```

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.

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.

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`.

The space form has one trap worth knowing: a builtin's name is a module *and* a
member (`__Map.nil` is two chain items), so a builtin passed as an argument to a
bare application keeps the chain going — `__Map.with __Map.nil .k 1` reads as
`((__Map.with __Map) .nil .k) 1`. Use the spread form whenever an argument is
itself a dotted name: `__Map.with(__Map.nil, .k, 1)`.

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 the pipe for match: `value |> __match [...]`

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))
]
```

`|>` is the loosest operator in a chain, so within a single segment it needs no
parentheses — and a pipeline that *is* a binder body does: write
`&x (x |> f)`, since `&x x |> f` pipes the lambda itself (`f (&x x)`). A
lambda as a whole pipe stage needs no parens: `x |> &y body` is `(&y body) x`.

## Mind the one-chain layout rule: a newline *is* a comma

An expression (and each `let` body) is a single muon chain, so newlines are load-bearing:

- A newline between a module's top-level moditems is free — a module file *is* a
  multi-chain sequence. It is a single-expression source (`elly::parse`, the
  playground) where a top-level newline splits the input 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`. `recur` has the same shape and the same trap
  (`RecurMissingBody`).
- A newline is safe *only* inside a `(…)` / `[…]` / `{…}` 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
```

**Reach for `recur` when the name is recursive.** A `let` binding cannot see
itself, so a recursive helper written as a `let` silently resolves its own name to
something outer, or fails to resolve it at all. Write the group with `recur`
instead — same layout, same newline rule — and keep `let` for the sequential case:

```elly
recur (
  loop = &(i, acc) (i |> __match [
    &(0)   acc
    &(> 0) loop(__Int.sub(i, 1), __Int.add(acc, i))
  ])
) loop(n, 0)
```

A `recur` binding may not reuse a name that is already in scope, so pick a fresh
one rather than shadowing the enclosing binder.

**Stopgap for top-level newlines.** A whole expression 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 for single-expression sources; a module file already has free
top-level newlines. The `(…)` wrapper also happens to sit nicely inside a Rust
multiline string literal (leading/trailing newlines are harmless).

## Symbols and keys, `{ key: ... }`, `{ .5: ... }`, `{ (5): ... }`

- 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.

## Imports go at the top, private by default

- Write a module's `import`s as its first lines, one per line, before the items.
  Nothing enforces it — an import is a declaration wherever it sits — but a
  module's imports are its reachable set, and a reader should not have to scan for
  them.
- Prefer the private form `import "spec" as Foo` and re-export deliberately.
  `Foo = import "spec"` says "callers of this module get `Foo` too", which is a
  promise about your interface, not a convenience.
- Name the import after what it is, not where it lives: `import "./util/text" as
  Text`. The spec string is the resolver's business; the name is your code's.
- Reading a member of an import is a two-item chain like any other, so it needs
  the spread form in argument position — `__Int.add(Dep.base, 1)`, not
  `__Int.add Dep.base 1` (see *Prefer the spread-call form*).

```elly
import "./log" as Log
Codec = import "./codec"        // re-exported: callers get M.Codec

encode = &v Log.trace(Codec.encode v)
```

## Module encapsulation: use `local ( foo = ... , bar = ... )` moditems by default

Don't forget to default to `local` definitions, unless the intent is to make them public.

```elly
// the top-levels items are public interface:
foo = &x x

// multiple local definitions (prefer to keep together and after public toplevels):
local (
  one = 1
  two = 2
)

// public again, but prefer to put before the first big `local` block:
baz = ...

// this is also private and can go in any order, but prefer one `local ( ... )` form:
local bar = ...
```

## Objects: `x.m(a)` when `x` is the subject, `M.m(x, a)` when the module is

An object is a payload plus a module as its prototype, and `x.m(a)` and `M.m(x, a)`
run the *same* item — so the choice is about what the line is about, not about
what works. Lead with the receiver for anything that reads as behaviour of a value
(`total.plus(step)`, `n.to_hms`), and with the module for what belongs to the type
as a whole — constructors above all (`Second.from_minutes(3)`, `Int.divrem(n, 60)`).

```elly
local Self = __module           // so signatures read `as Self`, not `as __module`

plus = &(__value n, other as Self) new(Int.add(n, other.total))

duration.plus(Second.zero)      // ✓ receiver first; the argument is spread
duration.plus Second.zero       // ✗ one chain: `duration .plus Second .zero`
```

- **Dispatch is spelled with the same dot as member access**, so a method argument
  that is itself a dotted name has to be spread — the second line above hands
  `.plus` the module `Second`, then projects `.zero` off the result. This is
  ergonomics §9 met from a third side; the spread form is the standing answer.
- **Reach the payload only through `__value`.** `&(__value n)` unwraps the
  receiver in the binder, which is what makes a method shorter than its module-form
  spelling; the payload has no other reader, and that is the encapsulation.
- **Accept your own kind with `as Self`.** A binary method (`plus`, `minus`) should
  say what it takes rather than trust whatever it is handed. The test is spelled
  `as __module`, but alias it once at the top — `local Self = __module` — and the
  `__` stays out of every signature below it.
- **Route every construction through one constructor.** An invariant is only worth
  stating if `__new` is unreachable from outside, so keep the checking constructor
  the single door and build the rest on it (`from_hours` → `from_minutes` → `new`).

`crates/elly-cli/tests/programs/Second.ly` is the worked example for all four, and
`Bool.ly` beside it is the iota case, where there is no payload to carry.

## Module `__main = ...`: prefer separate named items to be available outside

The smallest Elly program is:

```elly
// the `__main` entry is special and is only available to the host runner:
__main = &io io.print "hi"
```

This form is fine for a hello-world script, but keep in mind that a `main` might be
needed outside as well (e.g. for testing), so separate and name it explicitly:

```elly
// this entry is a regular exported moditem:
main = &io let (
  _ = io.print("hello")
  _ = io.print("world")
) []

// available to the host runner as well:
__main = main
```

Take the capability as a parameter wherever it is needed, rather than reaching for
it: `shout = &(io, s) io.print(s)`. A function's signature is then its permission
list — the whole point of passing `IO` as an argument instead of putting it in
scope.

Beware `banner = io.print("hi")` as an item. Item bodies re-evaluate on every
reference, so an effectful item fires per access rather than once. Keep effects in
what `__main` applies.
