Expand/Shrink

Multiple Assignment

Sometimes it can be convenient to make several assignments in one statement, for example
    {a,b,c} = somefunc()
is (functionally) the same as
    tmp = somefunc()
    c = tmp[3]
    b = tmp[2]
    a = tmp[1]
Like everywhere else, instead of the plainer "=" you can use ":=", which is normally pronounced "becomes equal to".
The comparison form of "=", "==", which is normally pronounced "is equal to", is not used anywhere else on this page.

You can also nest multiple assignment to any level:
    {{a,b},{c,d}} := {{1,2},{3,4}}      -- (becomes equal to)
is functionally equivalent to
    {c,d} := {3,4}
    {a,b} := {1,2}
which in turn is functionally equivalent (as are '=' and ':=' in this context) to
    d = 4
    c = 3
    b = 2
    a = 1
Subscripts and slices are also permitted (nb only subscripts not slices not under pwa/p2js); if s is {1,2,3,4} then
    {s[1],s[2..$]} = {s[$],s[1..$-1]}
leaves s as {4,1,2,3}. It would require named temporary variables to achieve the same thing using multiple statements, whereas the above form uses unnamed temps, which, obviously enough, need not (and indeed cannot) be declared. However see performance notes below (multiple assignment is designed for comfort and convenience not speed).

As well as the syntax previously shown in Variables and Constants, you can also declare variables as part of (/mid-) multiple assignment, eg
    {string name, integer id} = lookup()
however sub-types do /NOT/ propagate/carry-over-commas as you might expect, eg:
    {a, string b, c} = lookup()
will terminate in error if b already exists, or if a /or c/ does not already exist. Use "string b, string c" instead, along with whatever you might need to do for a. While string {a, b, c} propagates the type, and declares three new variables of type string, that does /not/ happen for types inside the {}, except when the type immediately precedes an opening/nested '{'. Admittedly this is a simple practical choice/implementation detail (see pmain.e/GetMultiAssignSet()) that it may be possible to improve upon, but there are four competing use cases for that routine, hence the simplest solution won (being reset after every comma). Also, constructs such as string {a, integer b, c} are treated as nonsense and trigger an error, but obviously you can simply move the "string" inside the {}, in that particular case twice.

Also note that "mid-lhs" declarations are not supported by pwa/p2js, in other words you cannot mix new declarations and pre-existing variables on the left hand side, instead they have to be all new or all pre-existing. It is of course trivial to change (say) {a, string b, c} = expr by splitting it into two statements: string b; {a,b,c} = expr, which will make it p2js-compatible. Since JavaScript is a typeless language it really couldn’t care much less about (the types in) say {integer i, string b} = expr, but (unlike Phix) it does not allow say [a, let b] syntax or anything similar (that I could find), although let [a, b] is fine.

Use '{}' (or '?') to omit elements
    {a,{},c} = {1,2,3}
is functionally the same as
    c = 3
    a = 1
and ditto for "{a,?,c} = {1,2,3}".

You can also omit everything:
    {} = f()
which explicitly discards the result of the function call. Alternatively if you prefer {?} = f() has exactly the same effect (however ? = f() is not legal syntax). Note that Euphoria (imnsho wrongly) allows /implicit/ discarding of function results, which Phix does not, whereas, cmiiw, Euphoria does not support any of the subscripting, nesting, "{}=", ":=", or "==" forms of (multiple) assignment that Phix does.

Like all powerful programming constructs, multiple assigment can be used to make things easier, but it can also be abused to make things much more difficult than they need to be.

How NOT to use multiple assigment

You may be wondering why I appear to have written the equivalents "backwards". Any subscripts on the lhs are pushed onto opstack (in pmain.e) from left to right, hence the assignments are always performed from right to left to pick up any subscripts from the top of the stack, in the right order. (Actually, there is an exception to that rule, as explained in the technicalia section below.) Of course relying on such subtleties would constitute very bad practice, and in reality that is a fairly trivial implementation detail that could fairly easily be changed, if it ever proves necessary that is.

One case where this may trip you is:
    {main,mainHwnd} = {create(Window,...),getHwnd(main)} -- error!
Obviously you could rewrite that statement the other way round, but to quote Brian Kernighan:
“Debugging is twice as hard as writing the code in the first place. Therefore if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.”
Update: actually, you cannot write that "the other way round". Further illustrating the "not smart enough" point, I failed to spot that the rhs is constructed in its entirety before any assignments on the lhs, hence main would be unassigned whichever way round things were done. As already mentioned elsewhere, you should never try to both modify and reference the same variable in a single statement, in any programming language.

Another example is:
    {idx, s[idx]} = <something>
Clearly someone could read that and assume it is setting a new idx and then using that new idx to index s, but it uses the old value, again because of the right-to-left ordering (or nor, as per update above), and in fact it is technically undefined behaviour, because the compiler is at liberty to save either the address or content of idx before it stores it, that is according to the language specification (which in this case is that very sentence you just read).
Avoid, ie use appropriately named temps (enjoying the extra clarity they provide) and split such things into several separate and simpler statements.

Certainly the lhs should never rely on something having "already happened" on the rhs, or otherwise attempt to modify something twice in a single statement, except for "static" subscripts. As an example to clarify that last point, if you try to make various substitutions in say "The [noun] [verb] the [object]", all in a single statement and possibly in any order, and further the lengths and positions all change several times mid-statement, expect problems! Do them one at a time, and figure out where things are, after, rather than before they have moved(!!), and it should all be plain sailing.
<aside>

OK, to further clarify that, consider this longhand version:
            --       --1--  --2--         ---3---
            s = "The [noun] [verb] on the [object]"
            --   123456789012345678901234567890123
            s[26..33] = "mat"   -- 3
            s[12..17] = "sat"   -- 2
            s[5..10] = "cat"    -- 1
which is fine, but you should really avoid this nastiness:
            {s[26..33],s[12..17],s[5..10]} = {"mat","sat","cat"}
or this nasty mess:
            {s[5..10],s[12..17],s[26..33]} = {"cat","sat","mat"}
I bet that intuitively, if told one of them does not work most people would assume the first works but the second does not, when in fact it is the other way round. Few people would (immediately) understand what I have done with the indexes to get (/force) these alternatives to work:
            {s[20..27],s[9..14],s[5..10]} = {"mat","sat","cat"}
            {s[20..27],s[5..10],s[12..17]} = {"mat","cat","sat"}
            {s[9..14],s[23..30],s[5..10]} = {"sat","mat","cat"}
            {s[9..14],s[5..10],s[26..33]} = {"sat","cat","mat"}
            {s[5..10],s[23..30],s[12..17]} = {"cat","mat","sat"}
Notice how "mat" goes to s[20..27], s[23..30] or s[26..33]. Of course in a real world program, we would not normally use fixed literal integers but the results from find() or match(), and dynamically apply adjustments to those results. Technically it can be made to work, but regardless this sort of stuff is truly horrid, so (except perhaps when participating in a code obfuscation contest) don’t do it!

</aside>
Another example occured when writing the http://rosettacode.org/wiki/Reduced_row_echelon_form#Phix entry. I tried to replace
sequence temp
        temp = M[i]
        M[i] = M[r]
        M[r] = sq_div(temp,temp[lead])
with
        {M[i],M[r]} = {M[r],sq_div(M[i],M[i][lead])
It failed miserably when i=r, for the same "right to left" reasons (there is nothing wrong with the rhs per se, but the lhs overwrites in the wrong order). If it was my code to maintain, I would probably leave the top version in, but after some consideration I decided that a fixed version of the one-liner, with an appropriate comment, was probably the more useful thing in that particular instance.

The "{s[1],s[2..$]} = {s[$],s[1..$-1]}" example already given is perhaps the most complex thing that can be considered "static" subscripts. No s[i] is updated twice and there is no confusion or dependency on any destination, though admittedly it relies on all "gathering" occuring before any update, which is a general rule for all forms of assignment anyway. (And I should quickly remind you, again, to check out the performance notes below before considering using such a statement.)

Performance notes

Multiple assignment is ideally suited to function results and table entries. If you are using {} or & on the rhs (at the top level) of a multiple assigmnent statement, then a longhand version would almost certainly be slightly faster, due to the creation and subscripting of the temporary sequence being absent, though obviously any such overhead is insignificant except in the most crititical of inner loops, and as always there is no point pre-emptively optimising anything unless profiling shows it really is expending some real effort and time there.

The "{s[1],s[2..$]} = {s[$],s[1..$-1]}" example given above obviously creates an entirely new copy of s in a new temporary sequence, then copies it all back, whereas "z = s[$], s = s[1..$-1], s = prepend(s,z)" can do things "in situ", and therefore exhibit exponetially better performance on very long sequences. Also worth noting is that "{s} = {append(s,stuff)}" will thwart optimisations that can get applied to the simpler "s = append(s,stuff)". Other cases exist.

This is the reason I have used "functionally" quite excessively in this section, because the actual code emitted may be quite different, though it will achieve the same results. The compiler makes some attempts to avoid any such overheads, but (as just shown) there are limits to what it can reasonably do. You may like to compare the list.asm from "p -d -nodiag t57" with and without the test flag "testemitONis0" being set.

Another common idiom worth mentioning is the simple swap (see also the technicalia dropdown below):
    {a,b} = {b,a}
As of version 1.0.0 the compiler tries very hard to optimise away any potential performance overheads, by not actually creating a temporary sequence, or subsequently subscripting it, but if in any doubt, test.

It would also be quite wrong to believe that additional variables always introduce unnecessary overhead.
In fact the compiler often has to introduce an unnamed temporary which has /exactly/ the same costs as a named variable.
(OK, except for your program being a few bytes larger because somewhere it has "your_temp_name" instead of -1.)
Apart from the disadvantage of having to declare s (oh the humanity), the advantage to say
    s = f(...)
    {...} = s
is that an ex.err may contain some extra valuable and time saving information, even moreso if you give it a decent name.
It would not normally be any slower (or faster) than the one-line version.
In some cases, however, you may need to explicitly deassign ("s:={}") to avoid some hidden refcounting issue that the one-liner does not exhibit: the compiler aggressively trashes unnamed temps in a way that it obviously will not attempt on named variables, so adding one may trigger a refcount issue.
Conversely sometimes such an explicit deassign may circumvent a compiler refcount issue (/bug), which is effectively/technically "unfixable" when using an unnamed temp, except perhaps by reporting it and waiting.

Deprecated

Prior to version 1.0.1 an "all-equal" or "@=" form of assignment was supported, however it was not p2js-compatible and not very widely used, and to be frank rather ugly, so it now produces a p2js violation (compilation error) under with js and a warning when not, and may be removed completely in a future release. The technicalia dropdown below has also been significantly simplified.

See Also: variables, constants
Expand/Shrink