Status
Accepted
Context
Slug already supports positional arguments and parameter default values. As Slug’s standard library and user code grow, functions naturally accumulate additional optional parameters (timeouts, headers, flags, etc.). Relying purely on positional arguments makes call sites harder to read and harder to evolve without breaking callers.
Slug also recently changed default-parameter evaluation so defaults evaluate in their defining module (not the caller’s context, see (ADR-18)[/adr/adr-007]). This enables module-owned defaults (constants/policies) to be central and reliable. Combining that capability with named arguments enables expressive, stable APIs without introducing “options map” patterns or implicit behavior.
Key goals:
- Improve readability of calls with many optional parameters.
- Allow callers to override only the parameters they care about.
- Keep semantics explicit and predictable.
- Keep runtime and VM impact small (named args should compile/resolve to positional binding).
- Avoid introducing dynamic “keyword bags” or option-object conventions.
Decision
Slug will support named parameters in function calls using call-site syntax:
Call-site syntax
Named arguments use name = expr inside the argument list.
var openFile = fn(path, mode = "r", buffering = true) { ... }
openFile("/tmp/data.txt", mode = "w")
openFile("/tmp/data.txt", buffering = false)
openFile("/tmp/data.txt", mode = "w", buffering = false)
Ordering rule
- Positional arguments must come first.
- Once a named argument is used, all remaining arguments must be named.
Valid:
f(1, 2, d = 10)
f(1, b = 2, d = 10)
Invalid:
f(a = 1, 2)
Parameter binding semantics
A function call binds arguments to parameters as follows:
- Allocate a “slot array” of length
paramCount, initially UNSET. - Fill slots left-to-right using positional arguments.
- Apply named arguments by looking up their parameter index by name.
-
For any remaining UNSET slots:
- If the parameter has a default, evaluate and fill it.
- Otherwise, raise an error for missing required parameter.
“Variadic Parameters and Named Arguments”
Key points to lock down:
- Variadic parameters collect excess positional arguments into a list.
- Variadic parameters may be assigned explicitly using a named argument.
- When assigned via name, the value must be a list.
- Positional arguments may not appear after named arguments, even when a variadic parameter exists.
- Unknown named parameters remain an error.
Validation rules
At runtime (or earlier if statically determinable), the following are errors:
- Unknown named parameter (name not in callee’s parameter list).
- Duplicate assignment to the same parameter (via repeated named args or via positional + named for the same parameter).
- Too many positional arguments.
- Missing required parameter (no argument and no default).
Interaction with default evaluation scope
Default expressions continue to evaluate in the defining module’s environment (not the caller’s environment). Named arguments override defaults by providing explicit values for those parameter slots.
Implementation notes (non-normative)
- The AST should represent call arguments as either positional or named (e.g.,
Name? + Value). - Function objects should maintain a name → index map for parameters to support efficient binding.
- The VM/runtime should execute calls using resolved positional slots; namedness should not be required after binding.
Consequences
Positive
- Readability: Call sites become self-documenting, especially when overriding optional parameters.
- API evolution: Libraries can add new optional parameters (typically at the end) with minimal breakage and improved ergonomics.
- Explicitness without option maps: Avoids “bag-of-options” patterns while still enabling flexible configuration.
- Synergy with module-scoped defaults: Libraries can define coherent, module-owned defaults (constants/policies) while callers override only the deltas.
- VM friendliness: Named arguments are resolved to positional slots before execution, keeping runtime and bytecode complexity low.
Negative
- Parser/AST complexity: Calls must distinguish
IDENT = exprfrom a normal expression within argument lists. - More validation paths: The runtime (or compiler) must check ordering, unknown names, duplicates, and missing required parameters.
- Potential confusion with assignment: Using
=in call sites could be misread as assignment by newcomers ( mitigated by clear documentation and examples).
Neutral
- No keyword “splatting”: There is no implicit expansion of maps into named arguments (by design).
- No named-only partial application: This ADR does not introduce currying/partial application behaviors; it only defines call-site binding.
- No reflection/introspection requirement: Named arguments do not become a runtime-visible map; they are a binding convenience.