# Please explain nuances of ||=

2008/5/2 Rick DeNatale [email protected]:

In summary:

That’s the way that Ruby works. Ruby ain’t C, or C++ or Java.

Nice summary! And, if you think about it from a usability perspective
it does make the most sense the way it is.

1. you do not want to spent CPU cycles for assigning the same object
to a reference

2. more importantly, in the case of Hash you do not want the Hash to
change (which it would do if ||= were implemented differently as I
have demonstrated earlier).

I’m out.

Cheers

robert

this controversy:

In writing this article, I pulled my old dusty copy of Kernighan and
Ritchie off my shelf, since IIRc the whole

x op= y

idea originally came from C.

And of course that’s where the oft reported meme that

x op=y

was identical to

x = x op y

originated, and was propagated through several popular expositions of
Ruby.

BUT

C, although it does have || and && as short-circuit boolean operators,
does NOT allow ||= or &&=

When Matz added these to Ruby he, quite cleverly IMHO, defined them as
short circuiting as well, the assignment is NOT done if the LHS
evaluates to true (in the Ruby sense that is, neither nil nor false).

And as I also verified, since reading about ||= in David Flanigan’s
“The Ruby P.ming Language” got me to thinking about it, &&= also
doesn’t do an assignment if the LHS evaluates to false.

All of this falls out naturally from 1) short-circuit evaluation of ||
and &&, and 2) the fact that a.x = y is syntactic sugar for a.x=(y),
and 3) a[b] = c is syntactic sugar for a.[]=(b,c).

So a foolish adherence to the idea that Ruby should slavishly follow C
where ||= and &&= are not allowable assignment operators, and there
are no x= or []= methods to serve as targets of syntactic sugar,
doesnt make that sense.

So:

1. In Ruby x ||= y does no assignment if x evaluates to true, and x
&&= y does no assignment if x evaluates to true.
2. x || x = y, and x && x = y are much better first approximations of
how x ||= y and x &&= y than x = x || y, and x = x && y, despite what
K&R say.
3. I say first approximations because of David Black’s observation
that x ||= y and x || x = y differ when x is undefined.

In summary:

That’s the way that Ruby works. Ruby ain’t C, or C++ or Java.

Get over it!

Rick DeNatale

My blog on Ruby

1. In Ruby x ||= y does no assignment if x evaluates to true, and x
&&= y does no assignment if x evaluates to true.

Eh, what? You are confusing me.

&&= does an assignment only if x evaluates to true.

The question of if there is an assignment in x = x is purely
metaphysical: there is no way to tell.

1. x || x = y, and x && x = y are much better first approximations of
how x ||= y and x &&= y than x = x || y, and x = x && y, despite what
K&R say.

And doesn’t explain why x springs into existence, if didn’t exist
before.

1. I say first approximations because of David Black’s observation
that x ||= y and x || x = y differ when x is undefined.

Right, but x = x || y doesn’t.

The only thing special about x ||= y is that x is only ever evaluated
once. But that’s special in every op=.

mfg, simon … l

irb(main):009:0> h[1] = h[1] || 10
=> true

Well, the difference is that h[1] gets only evaluated once.

But now I get it: It does make a difference here if there is an
assignment or not. So much for metaphysics.

So, neither x = x || y nor x || x = y explain everything about x ||= y.

&&= and ||= are different from += and co, because of their short-cut
behavior. They are conditional assignment operators.

mfg, simon … sfti

On Fri, May 2, 2008 at 8:35 AM, Simon K. [email protected] wrote:

1. In Ruby x ||= y does no assignment if x evaluates to true, and x
&&= y does no assignment if x evaluates to true.

Eh, what? You are confusing me.

&&= does an assignment only if x evaluates to true.

Yes, that was a typo on my part, it should have read “and x &&= y
does not assignment if x evaluates to false.”

The question of if there is an assignment in x = x is purely
metaphysical: there is no way to tell.

If x is a simple variable, that’s correct, however if x is actually an
“attribute accessor” like

foo.bar ||= y
or
foo[:bar] ||= y

Then you can tell if the bar= or []= method which gets invoked on
‘assignment’ has side effects.

1. x || x = y, and x && x = y are much better first approximations of
how x ||= y and x &&= y than x = x || y, and x = x && y, despite what
K&R say.

And doesn’t explain why x springs into existence, if didn’t exist
before.

This is because the Ruby parser recognizes the variable as local when
it sees it as the POTENTIAL assignee. In the first chunk below, the a
= 1 never got executed because of the if false modifier, but the
parser still picked up a ‘declaration’ of a as a local variable.

defined? a # => nil
a = 1 if false
defined? a # => “local-variable”
a # => nil

defined? b # => nil
b ||= 1
defined? b # => “local-variable”
b # => 1

defined? c # => nil
c &&= 1
defined? c # => “local-variable”
c # => nil

of course in the case of

d.e ||= g

or

h[1] ||= i

There’s no question of the method ‘springing’ into existence, d.e,
d.e=, h[], and h[]= will either work or throw a method not found if
and when they are called.

1. I say first approximations because of David Black’s observation
that x ||= y and x || x = y differ when x is undefined.

Right, but x = x || y doesn’t.

The only thing special about x ||= y is that x is only ever evaluated
once. But that’s special in every op=.

Except that, if x is really obj.foo, or obj[a] then the notion of x
getting evaluated once is a little squirrelly, because reading x is
done by evaluating obj.foo or obj., and writing it (should the
assignment actually occur) is done by evaluating obj.foo=(y) or
obj.[]=(a,y)

The fact that x = y is actually a method call under some circumstances
is what’s special about Ruby, and why simply extending K&Rs
explanation of op= misses the point.

Rick DeNatale

My blog on Ruby

2008/5/2 Rick DeNatale [email protected]:

1. I say first approximations because of David Black’s observation
that x ||= y and x || x = y differ when x is undefined.

They obviously also differ if evaluating x has side effects as in

h = {}
h[print(“f”)] ||= 1

h = {}
h[print(“o”)] || h[print(“o”)] = 1

# => foo

so x ||= y is more like

ref = &x; *ref || *ref = y

with the reference (&) and dereference (*) operators of C.

Regards,
Pit

Ruby 1.9 makes it somewhat easier to see what’s really happening.

Thanks a lot!

k\$ ruby1.9 -ve’puts VM::InstructionSequence.compile(“h={};h[“a” +
“b”] ||= 3”).disasm’

Can you give any documentation pointers for this cool stuff?

mfg, simon … l

On Fri, May 2, 2008 at 12:12 PM, Pit C. [email protected]
wrote:

h = {}
h[print(“o”)] || h[print(“o”)] = 1

# => foo

so x ||= y is more like

ref = &x; *ref || *ref = y

with the reference (&) and dereference (*) operators of C.

But x is different whether its on the RHS or LHS

The problem is that it’s not a textual substitution.

h[1+2] ||= 3 is like

arg_temp = 1 + 2
h[arg_temp] || h[arg_temp] = 3

Keep in mind that unlike operators like + and -, || and && are really
control flow ‘operators’ implemented by testing and branching. The
same is true of ||= and &&=, which I suppose is the point.

Ruby 1.9 makes it somewhat easier to see what’s really happening.

k\$ ruby1.9 -ve’puts VM::InstructionSequence.compile(“h={};h[“a” +
“b”] ||= 3”).disasm’
ruby 1.9.0 (2008-03-21 revision 0) [i686-darwin9.2.2]
== disasm: <ISeq:@>=================================
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1]
s1)
[ 2] h
0000 newhash 0 (
1)
0002 setlocal h
0004 getlocal h
0006 putstring “a”
0008 putstring “b”
0010 opt_plus
0011 dupn 2
0013 opt_aref
0014 dup
0015 branchif 28
0017 pop
0018 putobject 3
0020 send :[]=, 2, nil, 0,
0026 leave
0027 pop
0028 swap
0029 pop
0030 swap
0031 pop
0032 leave

I’m sure that Ruby 1.8 does pretty much the same thing, but I don’t
have the time to dig through parse.y and eval.c to prove it.

Rick DeNatale

My blog on Ruby