Please explain nuances of ||=

David A. Black wrote:

It's possible to generalize it; someone in one of the threads wrote a
class that showed the same behavior, but I can't remember the details.

···

--
vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

Someone already mentioned it, but I recently wrote an article about
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 Programming 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! <G>

···

--
Rick DeNatale

My blog on Ruby

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

···

2008/5/2 Rick DeNatale <rick.denatale@gmail.com>:

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.

--
use.inject do |as, often| as.you_can - without end

* Rick DeNatale <rick.denatale@gmail.com> (03:26) schrieb:

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

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

···

2008/5/2 Rick DeNatale <rick.denatale@gmail.com>:

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

* Rick DeNatale <rick.denatale@gmail.com> (03:26) schrieb:

> 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.

> 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.

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.

> 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=.

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.

···

On Fri, May 2, 2008 at 8:35 AM, Simon Krahnke <overlord@gmx.li> wrote:

--
Rick DeNatale

My blog on Ruby

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.

···

On Fri, May 2, 2008 at 12:12 PM, Pit Capitain <pit.capitain@gmail.com> wrote:

2008/5/2 Rick DeNatale <rick.denatale@gmail.com>:

> 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.

--
Rick DeNatale

My blog on Ruby