Handlers and aliases are lambdas; the calling convention is fixed by surface form
A handler or alias body is applied, never merely forced, so its arity is part
of its contract. Every per-name handler (within [handlers: [name: …]]) and
every alias must be a unary lambda { |args| … }, and every catch-all
(within [handler: …]) must be a binary lambda { |name args| … }. The calling
convention is chosen by the surface position — per-name/alias versus catch-all —
not inferred from the value’s runtime shape, and arity is validated at install
time: a bare block { … }, a non-lambda value, or a lambda of the wrong arity is
rejected with a clear error.
Why arity cannot be inferred from the value
A per-name handler is invoked with the command’s argument list; a catch-all is
invoked with the name and the argument list. Currying (cbpv,
docs/SPEC.md §4.5) makes the two indistinguishable by shape after the fact: a
binary lambda applied to a single argument does not error — it returns the inner
unary closure. So a binary lambda installed where a unary handler is expected
would not fault; it would hand the body a partial-application closure as the
intercepted command’s result — a silent wrong answer, exactly the
structural-bug-prevention failure
mode where a category error survives as a plausible-looking value. Pinning the
convention to the surface form and checking arity at install time makes that bad
state unconstructable rather than a runtime surprise.
What changes
Nullaryis removed. A bare block{ … }previously installed as a zero-argument handler, treated as nullary at dispatch. That form is gone: a bare block in handler, catch-all, or alias position is now an install-time error. The only valid handler/alias values are unary and binary lambdas.- The convention is positional. The per-name slot demands unary; the
catch-all slot demands binary; the
alias NAME { |args| … }builtin and the rc/pluginaliases:maps (docs/SPEC.md§9) demand unary. Dispatch (handler-dispatch) invokes the matched entry under the convention its install site fixed, never by inspecting the stored value. - Arity is checked once, at install.
within-frame installation,install_alias, and the rc/plugin alias loaders all reject a malformed value before it can reach lookup, so no wrong-arity entry is ever dispatched.
The hard rule
A per-name handler and every alias is { |args| … }; a catch-all is
{ |name args| … }. Anything else — a bare block, a non-lambda, a unary
catch-all, a binary per-name handler — is rejected where it is installed, with a
diagnostic naming the expected arity. The convention is read off the surface
form, never off the value.
See also effects-handlers,
handler-dispatch,
structural-bug-prevention,
alias-head-defines-its-modes;
the surface forms are docs/SPEC.md §3.2 (handlers) and §9 (aliases).