ADR-358: A Parameter May Take a Tuple Apart, in a Function and in a Lambda; the Ledger Names the Parts

Status: Accepted Date: October 15, 2026 Answers: whether a lambda’s parameter may take apart the tuple it is handed, and then a function’s too (issue #623) Related: ADR-356 D1, D2 (the tuple pattern of a let and a for head); ADR-291 (tuples, _); ADR-231 (what a walk hands its lambda); Part I 2.1 (Taking a tuple apart), 5.3; Part III 13.5, C.3

1. Question

May a parameter be a tuple pattern? A walk hands its lambda one item; where the item is a pair, the lambda can only name the pair and take it apart in its body. A let and a for head take a tuple apart where they bind (ADR-356); a parameter is the one binding place that cannot.

2. How others do it

From memory, not checked against a current manual.

Language A parameter that takes a tuple apart
Rust fn f((a, b): (i64, i64)) and \|(a, b)\|, in functions and closures alike
Kotlin { (a, b) -> } in a lambda; not in a function
Swift { (a, b) in } in a closure, after SE-0110 brought the special case back
JavaScript ([a, b]) => … and function f([a, b])
Python removed in Python 3 (PEP 3113): a signature is an object asked at run time, the pattern had no name (.0), annotations and keyword calls could not reach it, and it was seldom used

Where it exists it is marked with brackets; no language takes a pair apart by the count of the parameters alone.

3. Options

Option Why it lost, or won
1 Refuse it; let (a, b) = pair in the body one more line per lambda over pairs, and the one binding place without a pattern
2a A bracketed pattern in a lambda only a form a lambda has and a function not; two rules where one does
2b A bracketed pattern in a function and a lambda chosen (D1-D5)
3 fn(a, b) takes a pair apart by count the meaning would follow an inferred type: a changed item type turns a destructuring into an arity error or back (Swift’s SE-0110)

4. Decision

D1 — A positional parameter may be one irrefutable tuple pattern, in a function and in a lambda.

fn dist((x, y): (f64, f64)) -> f64 { … }
pairs.iter().filter(fn((name, v)) { v > 1 })

The pattern is ADR-356 D1’s: a name, _, or a parenthesised list of two or more patterns, nested as written; (p) is p. A function writes the type of the whole parameter after it, as for any parameter; a lambda’s is the item the place hands it. A variant is NK1259 (ADR-356 D2). An option, which a call names, is never a pattern.

D2 — The parameter is one parameter.

The function’s type, its count of arguments and every caller see one parameter of the tuple’s type: fn((String, i64)) -> bool. The pattern binds names inside the body and is seen nowhere else. fn(a, b) stays two arguments, and a lambda with two names that is handed one tuple stays NK1101, whose help writes the pattern: fn((name, ty)).

D3 — mut and keep belong to the whole parameter.

Changed by ADR-views-3 D2, D4: mut and keep belong to the whole parameter; ref is not written.

(a, b): (String, i64) lends each part as a view of the argument’s part; mut (a, b): … makes each part changeable, and a change reaches the caller as for any mut parameter; keep (a, b): … keeps the whole argument.

D4 — The ledger names a pattern parameter by its parts.

The signature writes the pattern as the source does, "((a, b): (ref String, ref String)) -> ref(a | b) String", and every other key that names a parameter (keeps, returns, throws’ from(p), code, maps, views, touches, sharing) names a part by its name, which is unique in the function. A caller reads a part as the component of its argument at that position. A message names such a parameter as it is written.

D5 — It lowers as a parameter with a name of the compiler’s and a let.

The parameter takes a name nobody can write, and the body begins by taking it apart, with ADR-356’s lowering; a part of a borrowed tuple is read as the for head reads it.

5. Reasons

Rejected: 1, 2a and 3, above.

6. Consequences

This site is open source. Improve this page.