Skip to content

docs: SPEC's "one rule, one sentence" for of is false for arity-1 callees — the implementation is right and load-bearing #635

Description

@InauguralPhysicist

Finding

docs/SPEC.md:626 states:

One rule, one sentence: brackets after of are an argument list; parentheses are one argument.

docs/LANGUAGE_CONTRACT.md:208-213 repeats it:

  • f of [a, b]two arguments. So momentum of [2, 3] passes…
  • Extra elements are ignored; parameters with no matching element take their default, else null.

Neither holds for a 1-parameter callee.

Reproduction

define one(a) as:
    return a
print of ["one of [3, 4] ->", one of [3, 4]]
print of ["type          ->", type of (one of [3, 4])]
["one of [3, 4] ->", [3, 4]]     <- docs promise a = 3
["type          ->", "list"]

one of [3, 4, 5][3, 4, 5]. Identical under EIGS_JIT_OFF=1 and for lambdas. --lint reports "no issues found".

A reader of either doc writes one of [3, 4] expecting a = 3, gets the whole list, rc=0.

The docs are the wrong side — the behavior is load-bearing

src/vm.c:1795-1806 / 1822-1837 re-collect all args into a list when param_count == 1 && argc != 1 && !can_default. That is exactly what makes these work:

len of [1, 2]     -> 2        (not len of 1)
print of [1, 2]   -> [1, 2]

Removing it would break the language. So the real rule is arity-dependent: for a 1-param non-defaulted callee, f of [][], f of [x]x, f of [x, y][x, y].

This is the skill's counter-rule in action — the "silent" path is the semantics, not a bug. Checked before condemning it.

Why it matters

of is the language's single most distinctive syntax, and the doc's whole selling point is that it's one sentence. It isn't. The carve-out is invisible until a user writes a 1-param function and passes it a list — at which point they get a silently wrong value, not an arity error.

Note the f of [] half of the carve-out is already documented at LANGUAGE_CONTRACT.md:264-268. The argc >= 2 half is documented nowhere.

Fix (docs only)

  1. docs/SPEC.md call section + the evaluation-model reference: state the arity-1 carve-out next to the "one rule" sentence.
  2. docs/LANGUAGE_CONTRACT.md argument-unpacking section: same, alongside the existing f of [] note.
  3. Add an executable-doc example so the gate pins it:
    define one(a) as:
        return a
    print of (type of (one of [3, 4]))
    
    list
  4. Consider extending lint W017 (which already flags the 1-element bare form) to flag a bare multi-element list passed to a statically-known 1-param callee — that's the case where the user's intent and the semantics most likely diverge.

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions