Brackets and the call convention

Koskari reads ( ) and [ ] with the same grammar. A [ ] form is not a vector, an array, or a list literal. Both read into pair chains, and by default both compile the same way.

Same grammar, same compilation

A non-empty list in head position compiles to a call, whichever bracket delimited it:

(f 1 2)   ; call f with 1 and 2
[f 1 2]   ; the same call

[a 1] is a call to a with argument 1. A reader arriving from a language where [...] builds a list has this one thing to unlearn. Collection values are the #(...), #[...], and #{...} reader forms: tuple, list, dict.

What the brackets carry

The reader records that a form came from [ ] by producing a bracket-pair, and bracket-nil for the empty []. A bracket-pair is value-equal to a pair, every pair operation works on it, and it remembers its bracket origin. That memory does nothing on its own; it is there for tooling and for the compiler’s call-position check.

The convention

By default the compiler points the convention out rather than enforcing it, with a SyntaxWarning and no change to compilation:

  • ( ) is a call. The first element is applied to the rest. Some forms evaluate every argument; others treat certain arguments as data.

  • [ ] is a data fragment that the enclosing form consumes: a binding list, a cond clause, a pattern row.

The convention is a reading aid first. Nobody reads

(let [[a 1]] (+ a 100))

as a call of a with 1; the brackets say “binding” at a glance. A binding list is consumed as data by let and never reaches the call compiler, whatever the convention says.

Catching a typo

Because brackets mark data positions, a bracketed fragment that reaches call position is a sign of a mistake. With ler typed for let:

(ler [[a 1]] (+ a 100))

ler is not a special form, so the form compiles as a call of the value ler whose first argument is [[a 1]], that is, (call (call a 1)). The bracket marker lets the compiler see a [ ] fragment in call position and say so.

Changing the behaviour

Two settings, changed only by a pragma near the top of a module; there is no global switch and no per-form syntax:

Setting

Affects

Values

Default

Meaning

reader/brackets

reader

on, off

on

whether [ ] reads into a bracket-pair or a plain pair

compiler/call-brackets

compiler

ignore, warn, error

warn

what happens when a bracket-pair reaches call position

;; a [ ] fragment in call position is a compile error
(pragma [compiler/call-brackets 'error])

;; the check is off; brackets are documentation
(pragma [compiler/call-brackets 'ignore])

;; no marker is produced, so no check can fire
(pragma [reader/brackets False])

With error, the ler typo above is caught at compile time. A pragma is forward-only and scoped to its module: it affects the code after it and never the modules it imports.

See also