Please explain nuances of ||=

R

Robert Klemme

2008/5/2 Rick DeNatale said:
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.

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
 
S

Simon Krahnke

* Rick DeNatale said:
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.
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.

And doesn't explain why x springs into existence, if didn't exist
before.
3) 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
 
S

Simon Krahnke

* Robert Klemme said:
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
 
R

Rick DeNatale

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.
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.
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.[](a), 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.
 
P

Pit Capitain

2008/5/2 Rick DeNatale said:
3) 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
 
R

Rick DeNatale

2008/5/2 Rick DeNatale said:
3) 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.

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:<compiled>@<compiled>>=================================
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, <ic>
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.
 
S

Simon Krahnke

* Rick DeNatale said:
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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,204
Messages
2,571,066
Members
47,672
Latest member
svaraho

Latest Threads

Top