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