Invisible link to canonical for Microformats

ADR-018 Default Parameter Evaluation Scope


Status

Accepted

Context

Slug supports default parameter values in function definitions. These defaults are expressed as ordinary Slug expressions and are evaluated at function call time rather than at function definition time.

This design enables expressive defaults such as configuration lookups:

fn connect(
  host = cfg("db.host", "localhost"),
  port = cfg("db.port", 5432)
) { ... }

However, ambiguity arises when a default parameter expression references identifiers that are local to the function’s defining module but are not exported. Since defaults are evaluated at call time, a naïve interpretation would attempt to resolve identifiers in the caller’s lexical environment, which may not contain those bindings.

This creates a conflict between:

  • lexical scoping
  • module encapsulation
  • expressive, non-literal default values

The language must clearly define where default parameter expressions are resolved in order to remain predictable, explicit, and consistent with Slug’s design philosophy.

Decision

Default Parameter Name Resolution

Default parameter expressions are resolved in the function’s defining module environment, not the caller’s environment.

  • Name resolution for default expressions uses:

    • module-local bindings
    • imports of the defining module
  • Default expressions do not see:

    • caller-local bindings
    • caller imports
    • dynamic runtime scope

Evaluation Timing

  • Default parameter expressions are evaluated at call time
  • No evaluation occurs at function definition time
  • No implicit closure capture is introduced

Conceptual Model

Default parameters are treated as part of the function definition, not part of the call site.

“Defaults belong to the function, not the caller.”

Example

// my/db.slug
val dbHost = cfg("db.host", "localhost")

fn connect(host = dbHost) {
  ...
}
// app.slug
import my.db

db.connect()   // ✔ dbHost resolves within my/db module

The caller does not need access to dbHost, nor does it need to be exported.

Consequences

Positive

  • Preserves module encapsulation
  • Allows defaults to reference private implementation details
  • Enables expressive defaults (e.g. cfg, computed values)
  • Avoids hidden exports or API surface pollution
  • Keeps default parameters simple and predictable
  • Requires no additional runtime overhead
  • Aligns with Slug’s emphasis on explicit boundaries

Negative

  • Defaults cannot reference caller-local variables
  • May surprise users expecting caller-scope resolution

This behavior is consistent and easily documented.

Neutral

  • Default expressions behave similarly to helper functions defined within the same module
  • Named parameters and pattern-based dispatch are unaffected
  • No impact on runtime semantics beyond name resolution rules