Multiple assignment in conditional

I find this a strange Ruby error.

foo = [1,2]

The following is fine.

if (a = foo)
puts a
end

The following causes a SyntaxError: multiple assignment in

conditional.
if (a, b = foo)
puts a
end

Why would that be a syntax error? Surely the grammar is

if EXPR

and (a, b = foo) is an expression, isn’t it?

Just curious, though my curiosity was prompted by a failing piece of
real
code.

Gavin

On Tue, Jan 31, 2012 at 12:46 AM, Gavin S.
[email protected]wrote:

if (a, b = foo)

Just curious, though my curiosity was prompted by a failing piece of real
code.

Gavin

What did you expect your real code to do? It seems to me that it should
raise an error unless it’s an array of size 2, and if it is, then it
should
return an array of size two, making the conditional necessarily true. So
I
don’t know what you were expecting to happen.

Hi Gavin,

On 31/01/12 17:16, Gavin S. wrote:

if (a, b = foo)

Just curious, though my curiosity was prompted by a failing piece of real
code.

I can understand why the language rejects that construct (I’ve never
tried it myself- an interesting find!). The method itself would be
returning an array (correct?), and an array would always be treated as
true in the expression, regardless of the results. Thus, it is likely to
be some sort of error (why put an always-statically-true expression in a
conditional?), and hence knocks it back with an error.

I’m wondering as to the best behaviour if it were actually handled
directly by the language (which I’m not specifically suggesting). True
if any of the multiple assignments evaluate to true? Or just the first?
Or last? Certainly not always-true. I can’t say I’d know a sensible
default. :confused:

Garth

On Tue, Jan 31, 2012 at 08:57, Garthy D
[email protected] wrote:

On 31/01/12 17:16, Gavin S. wrote:

The following causes a SyntaxError: multiple assignment in conditional.

if (a, b = foo)

I can understand why the language rejects that construct (I’ve never tried
it myself- an interesting find!). The method itself would be returning an
array (correct?), and an array would always be treated as true in the
expression, regardless of the results. Thus, it is likely to be some sort of
error (why put an always-statically-true expression in a conditional?), and
hence knocks it back with an error.

if true
puts ‘well that’s a relief’
end

I’m wondering as to the best behaviour if it were actually handled directly
by the language (which I’m not specifically suggesting). True if any of the
multiple assignments evaluate to true? Or just the first? Or last? Certainly
not always-true. I can’t say I’d know a sensible default. :confused:

[a, b, …].any?

Hi Nikolai,

On 31/01/12 18:57, Nikolai W. wrote:

array (correct?), and an array would always be treated as true in the
expression, regardless of the results. Thus, it is likely to be some sort of
error (why put an always-statically-true expression in a conditional?), and
hence knocks it back with an error.

if true
puts ‘well that’s a relief’
end

Haha, I knew someone would call me out on that one if I wasn’t more
specific. What I didn’t expect was how little time it’d take. :wink:

To clarify: If “if (a, b = foo)” would always evaluate to true, it might
cause confusion for someone expecting it to base the evaluation on one
of the assigned variables; whereas the intent for “if true” is pretty
clear; it’s an expression with less than two assignments.

Still, it’s not necessarily a strong argument or one I’d get behind. I’m
just saying that I understand why it might have turned out that way.
Maybe the potential for confusion led to that particular design
decision?

I’m wondering as to the best behaviour if it were actually handled directly
by the language (which I’m not specifically suggesting). True if any of the
multiple assignments evaluate to true? Or just the first? Or last? Certainly
not always-true. I can’t say I’d know a sensible default. :confused:

[a, b, …].any?

Indeed, that’s another possibility, although I’ll point out that it’s
inconsistent with an empty array normally being treated as true in an
ordinary expression. But hey, we are working with an odd case here, so
why not? :slight_smile:

Garth

On Tue, Jan 31, 2012 at 2:46 PM, Gavin S. [email protected]
wrote:

The following causes a SyntaxError: multiple assignment in conditional.

if (a, b = foo)
puts a
end

because it’s a parallel assignment and ruby would not know which to
check, a or b, no?

try eg,

a,b=true
or
a,b,c=1

kind regards -botp

On Tue, Jan 31, 2012 at 8:38 PM, botp [email protected] wrote:

try eg,

a,b=true
or
a,b,c=1

ok, ignore. i get gavin’s point regarding expression

On Tue, Jan 31, 2012 at 7:46 AM, Gavin S. [email protected]
wrote:

if (a, b = foo)
puts a
end

Why would that be a syntax error? Surely the grammar is

if EXPR

and (a, b = foo) is an expression, isn’t it?

Yeah, I’d agree. But why write such obfuscated code?

Just curious, though my curiosity was prompted by a failing piece of real
code.

Well, there is no point in having assignments in conditionals - unless
it’s a loop in which case the code often becomes more elegant, e.g.:

while ( line = gets )
puts line
end

For a simple if and unless there is really no advantage in having an
assignment in the condition.

Kind regards

robert

On Tue, Jan 31, 2012 at 6:22 PM, botp [email protected] wrote:

also elegant.
if ruby would support that, wouldn’t it be elegant?

best regards -botp

I don’t think it would be elegant, because the relationship between the
condition and the assignment is obscure. I would assume it’s a shorthand
for loop { line, x, y = get_something; ... } but would have to look at
it
for a while and then run some experiments to see.

On Wed, Feb 1, 2012 at 6:16 AM, Robert K.
[email protected] wrote:

it’s a loop in which case the code often becomes more elegant, e.g.:

while ( line = gets )

i dont know, Robert, since i also find

while ( line,x,y = get_something)

also elegant.
if ruby would support that, wouldn’t it be elegant?

best regards -botp

On Wed, Feb 1, 2012 at 1:22 AM, botp [email protected] wrote:

also elegant.
if ruby would support that, wouldn’t it be elegant?

I find

something.each do |line, x, y|

end

more elegant and idiomatic Ruby. That’s why I typically use

ARGF.each do |line|

end

instead of while ( line = gets ).

Kind regards

robert

On Tue, Jan 31, 2012 at 6:22 PM, Josh C. [email protected]
wrote:

The following causes a SyntaxError: multiple assignment in conditional.

if (a, b = foo)
puts a
end

What did you expect your real code to do? It seems to me that it should
raise an error unless it’s an array of size 2

Nope. It can return anything at all and Ruby will deal with it.
Parallel assignment is (thankfully) flexible.

def foo; 1; end
a, b = foo # returns 1; a == 1, b == nil

def bar; [1,2,3,4,5]; end
a, b = bar # returns [1,2,3,4,5]; a == 1, b == 2

and if it is, then it should
return an array of size two, making the conditional necessarily true.

Nope. The conditional can be false.

def foo; nil; end
a, b = foo # returns nil; a == nil, b == nil

So I don’t know what you were expecting to happen.

def foo; nil; end

if (a, b = foo)
puts “outcome 1”
else
puts “outcome 2” # I was expecting this.
end

Of course I don’t have a function that returns nil unconditionally!
Just showing the structure.

On Wed, Feb 1, 2012 at 10:57 PM, Gavin S. [email protected]
wrote:

Sorry to reply to my own message, but I thought of something I should
have included.

Here is the forbidden version I would like to write. Obviously I’m
combining assignment and conditional, which may not win plaudits as a
general rule.

def week_and_day(date)
if not self.include? date
return nil
elsif (week, day = @t1.week_and_day(date))
return [week, day]
elsif (week, day = @t2.week_and_day(date))
week += @t1.number_of_weeks
return [week, day]
end
end

But consider this alternative, where I separate the assignment and the
conditional.

def week_and_day(date)
if not self.include? date
return nil
else
week, day = @t1.week_and_day(date)
if week
return [week, day]
else
week, day = @t2.week_and_day(date)
week += @t1.number_of_weeks
return [week, day]
end
end
end

I consider this very clumsy. By not combining assignment with
conditional, I cannot use “elsif”. I must split the “else” and the
“if”, and turn the second conditional into a child instead of a
sibling.

Definitely less intentional and unattractive code.

Gavin

On Wed, Feb 1, 2012 at 1:05 PM, Gavin S. [email protected]
wrote:

if not self.include? date
conditional.
week += @t1.number_of_weeks
Definitely less intentional and unattractive code.
There are plenty more solutions, for example:

def week_and_day(date)
return nil unless include? date

week_and_day = @t1.week_and_day(date) and return week_and_day

week_and_day = @t2.week_and_day(date)
week_and_day[0] += @t1.number_of_weeks if week_and_day

week_and_day
end

Maybe also

def week_and_day(date)
return nil unless include? date

case
when week_and_day = @t1.week_and_day(date)
nil
when week_and_day = @t2.week_and_day(date)
week_and_day[0] += @t1.number_of_weeks
end

week_and_day
end

Kind regards

robert

On Wed, Feb 1, 2012 at 11:33 PM, Robert K.
[email protected] wrote:

There are plenty more solutions, for example:

Both contain this, which I think is rather ugly:

week_and_day[0] += @t1.number_of_weeks

Ruby is beautiful for many reasons. One of those is the ability to
return multiple values in a nice syntactic way. These solutions are
not embracing that; they are working with an array instead of with two
values.

Alternatives aside, what do you think of my preferred solution?

Gavin

On Wed, Feb 1, 2012 at 1:42 PM, Gavin S. [email protected]
wrote:

return multiple values in a nice syntactic way. These solutions are
not embracing that; they are working with an array instead of with two
values.

Alternatives aside, what do you think of my preferred solution?

On first sight it looks like it’s probably the best solution for the
given setting. But I cannot get rid of that nagging feeling in the
back of my head that something is totally wrong here. I just can’t
put my finger on it. :slight_smile:

Maybe it’s that we have methods here which are intended to return two
values but actually there is a special case of nil being returned for
the array of two.

I can’t test on 1.9.* right now but in 1.8.7 (a,b=nil) is always an
empty array => true. So if the method returns a single value nil you
probably do not get away without assigning that single value to a
variable and go from there. Or you test one of the array elements
only for nil.

Kind regards

robert

On 02/01/2012 03:49 AM, Gavin S. wrote:

def foo; nil; end

if (a, b = foo)
puts “outcome 1”
else
puts “outcome 2” # I was expecting this.
end

You can trick ruby:

def foo; nil; end

if (_ = (a, b = foo))
puts “outcome 1”
else
puts “outcome 2”
end

So, the question is parsing, not semantics.

On Wed, Feb 1, 2012 at 5:49 AM, Gavin S. [email protected]
wrote:

return an array of size two, making the conditional necessarily true.
if (a, b = foo)
puts “outcome 1”
else
puts “outcome 2” # I was expecting this.
end

Of course I don’t have a function that returns nil unconditionally!
Just showing the structure.

Sorry, but I find this incredibly opaque.

Could you just write what you expect the evaluation to be in each of
these
cases?

a, b = 1, 1
a, b = 1, nil
a, b = nil, 1
a, b = nil, nil

I’d expect return values of [1, 1], [1, nil], [nil, 1], [nil, nil] which
are all true.

Now does this change when the RHS is an array instead of two values?
For example, would a, b = [1, 1] return a different value than a, b = [1, 1]?

Your example seems particularly devious in that it only has one value on
the RHS of the assignment. This means that the second argument will be
assigned nil (because there is no value for it), and because assignment
operators always return the RHS, it will then return whatever single
value
was over there. This causes it to look like you are assigning multiple
values to the variables and having it return nil, when in fact you are
only
assigning one value (the second variable is along for the ride and will
always be nil).

There are so many nuanced ruby edge cases one must know (and then must
also
know the implementation of how it is being returned) to understand that
example that I think it has more overhead than it is worth.

On Thu, Feb 2, 2012 at 4:15 AM, Joel VanderWerf
[email protected] wrote:

So, the question is parsing, not semantics.

indeed. ruby just checks the last assignment.

if (x,y=(a, b = foo))
p 1
end
SyntaxError: (irb):17: multiple assignment in conditional

but i do not know why it has to do that checking. too much work. and
could probably slow down ruby.

kind regards -botp

On Wed, Feb 1, 2012 at 9:16 AM, Robert K.
[email protected] wrote:

Yeah, I’d agree. But why write such obfuscated code?

Here’s the code with the thing that doesn’t work. Of course you don’t
know the context, but I hope this is enlightening.

week_and_day(date) → [17,1] week 17, day 1 (Mon)

Returns nil if the given date is not in this term.

def week_and_day(date)
if not self.include? date
return nil
elsif (week, day = @t1.week_and_day(date))
return [week, day]
elsif (week, day = @t2.week_and_day(date))
week += @t1.number_of_weeks
return [week, day]
end
end

I don’t see anything obfuscated about that. @t1.week_and_day(date)
will return nil if the date is outside the range of @t1, so we will go
to the next part of the conditional.

Here it is in acceptable Ruby.

week_and_day(date) → [17,1] week 17, day 1 (Mon)

Returns nil if the given date is not in this term.

def week_and_day(date)
if not self.include? date
return nil
elsif (week_and_day = @t1.week_and_day(date))
return week_and_day
elsif (week_and_day = @t2.week_and_day(date))
week, day = week_and_day
week += @t1.number_of_weeks
return [week, day]
end
end

This one has a clumsy variable name “week_and_day” and an extra line
to unpack it into “week” and “day”. I’m not crying mercy over one
extra line, but I definitely prefer the first version. It seems more
intentional.

Gavin