Multiple Value Assignment¶
DEP #: |
13 |
Type: |
Standards Track |
Affects-DRM: |
Yes |
Author: |
Carl Gay |
Status: |
Draft |
Created: |
10-Dec-2024 |
Last-Modified: |
10-Dec-2024 |
Post-History: |
None |
Target-Version: |
2025.1 |
Abstract¶
Add new syntax to assign multiple variables from a single multi-valued expression.
Specification¶
The DRM defines the syntax of assignment expressions to be:
place := new-value
This DEP adds new syntax on the left hand side to allow for multiple places to be set
from the multiple values returned by the new-value expression, which we rename here
to new-values:
(place1, place2, ...) := new-values
The first return value is assigned to place1, the second value to place2, etc. The
last place may be preceded by #rest, in which case it is assigned a sequence
containing all the remaining values.
If there are more places than values returned by new-values, the extra places are
assigned the value #f.
The name _ (underscore) may be used to indicate that a value is to be ignored. For
example, (_, remainder) := round(3.2) will ignore the first return value and assign
remainder the value 0.2.
The order of assignment of values to places is unspecified.
BNF Changes¶
No changes are required to the Dylan BNF as described in the DRM. LEAF := EXPRESSION
is already valid and ( EXPRESSION ) syntax is a valid LEAF production.
Rationale¶
Multiple value assignment syntax naturally matches the existing multiple value binding
syntax provided by let and can occasionally be a useful tool to make code more
concise.
Examples¶
Some examples from the Open Dylan sources that can be made more concise:
// This occurs a lot in dfmc/conversion/convert.dylan
let (f, l) = join-2x1!(first, last, comp); first := f; last := l; // old
(first, last) := join-2x1!(first, last, comp); // new
// old:
define sealed method set-union!
(set1 :: <bit-set>, set2 :: <bit-set>) => (set1 :: <bit-set>)
let (vector, pad)
= bit-vector-or!(set1.member-vector, set2.member-vector,
pad1: set1.member-vector-pad,
pad2: set2.member-vector-pad);
set1.member-vector := vector;
set1.member-vector-pad := pad;
set1
end method;
// new:
define sealed method set-union!
(set1 :: <bit-set>, set2 :: <bit-set>) => (set1 :: <bit-set>)
(set1.member-vector, set1.member-vector-pad)
:= bit-vector-or!(set1.member-vector, set2.member-vector,
pad1: set1.member-vector-pad,
pad2: set2.member-vector-pad);
set1
end method;
// old:
for (i from 0 below n)
let (thread, result) = join-thread(threads[i]);
results[i] := result;
end for;
// new:
for (i from 0 below n)
(_, results[i]) := join-thread(threads[i]);
end for;
// old:
define method stop-profiling-type
(state :: <profiling-state>, keyword :: <cpu-profiling-type>) => ()
when (element(state, #"cpu-profiling", default: #f))
let (seconds, microseconds) = timer-stop(state[#"cpu-profiling-timer"]);
state[#"cpu-time-seconds"] := seconds;
state[#"cpu-time-microseconds"] := microseconds;
state[#"cpu-profiling"] := #f;
state[#"cpu-profiling-timer"] := #f;
end
end method stop-profiling-type;
// new:
define method stop-profiling-type
(state :: <profiling-state>, keyword :: <cpu-profiling-type>) => ()
when (element(state, #"cpu-profiling", default: #f))
(state[#"cpu-time-seconds"], state[#"cpu-time-microseconds"])
:= timer-stop(state[#"cpu-profiling-timer"]);
state[#"cpu-profiling"] := #f;
state[#"cpu-profiling-timer"] := #f;
end
end method stop-profiling-type;
// old:
let (sec, nsec) = %timer-current-time();
timer.timer-started-seconds := sec;
timer.timer-started-nanoseconds := nsec;
// new:
(timer.timer-started-seconds, timer.timer-started-nanoseconds)
:= %timer-current-time();
// old:
let (name, #rest arguments) = tokenize-command-line(command-line);
*application-name* := name;
*application-arguments* := apply(vector, arguments);
// new:
(*application-name*, #rest *application-arguments*) := tokenize-command-line(command-line);
// old:
let (ins, outs) = split-operation-arguments(request-arguments(request));
request-in-args(request) := ins;
request-out-args(request) := outs;
// new:
(request-in-args(request), request-out-args(request))
:= split-operation-arguments(request-arguments(request));
// old:
let (_n, _what) = goto-position-dialog(window, what | #"line");
n := _n;
what := _what
// new:
(n, what) := goto-position-dialog(window, what | #"line");
// old:
let (width, height) = frame-size(dialog);
$buffer-box-width := width;
$buffer-box-height := height;
// new:
($buffer-box-width, $buffer-box-height) = frame-size(dialog);
Alternatives Considered¶
One might consider it desirable to allow the left hand side of
:=expressions to omit the parentheses in multiple value assignments when it is unambiguous. That is, to allow both(a, b) := f()anda, b := f()as long as no ambiguity exists.In certain contexts parentheses would be required for disambiguation, for example in a argument list context:
vector(a, b := f())
Is the above equivalent to
vector(a, f())orvector(f())?vector((a, b) := f())
The above is unambiguously equivalent to
vector(f())since multiple value assignment always returns the first assigned value.However, in a body context (i.e., a sequence of semicolon delimited statements) omitting the parentheses may be desirable as it is less “noisy”:
...; a, b := f(); ...
Decision: To remove any ambiguity, and to match the existence of parentheses for multiple value bindings with
let, parentheses will be required for multiple value assignments.
Reference Implementation¶
TODO: permalink
Revision History¶
The revision history of this document is available here: https://github.com/dylan-lang/opendylan/commits/master/documentation/source/proposals/dep-0013-multi-assignment.rst