The ||= assignment operator

B

Belorion

[Note: parts of this message were removed to make it a legal post.]

It was my understanding that the ||= assignment operator assigned the value
on the right-hand side if and only if the left hand side did not already
have a value:

irb(main):001:0> x = true
=> true
irb(main):002:0> x ||= "ruby"
=> true
irb(main):003:0> x
=> true
And, likewise, with nil:

irb(main):014:0> x = nil
=> nil
irb(main):015:0> x ||= "ruby"
=> "ruby"
irb(main):016:0> x
=> "ruby"
However, I do not understand this behavior:

irb(main):019:0> x = false
=> false
irb(main):020:0> x ||= "ruby"
=> "ruby"
irb(main):021:0> x
=> "ruby"


We know that false != nil, and yet the ||= will assign if the left hand side
is false?

regards,
Matt
 
G

Glen Holcomb

[Note: parts of this message were removed to make it a legal post.]

It was my understanding that the ||= assignment operator assigned the
value
on the right-hand side if and only if the left hand side did not already
have a value:

irb(main):001:0> x = true
=> true
irb(main):002:0> x ||= "ruby"
=> true
irb(main):003:0> x
=> true
And, likewise, with nil:

irb(main):014:0> x = nil
=> nil
irb(main):015:0> x ||= "ruby"
=> "ruby"
irb(main):016:0> x
=> "ruby"
However, I do not understand this behavior:

irb(main):019:0> x = false
=> false
irb(main):020:0> x ||= "ruby"
=> "ruby"
irb(main):021:0> x
=> "ruby"


We know that false != nil, and yet the ||= will assign if the left hand
side
is false?

regards,
Matt

Nope ||= will assign if nil or false.
 
T

Todd Benson

It was my understanding that the ||= assignment operator assigned the value
on the right-hand side if and only if the left hand side did not already
have a value:

irb(main):001:0> x = true
=> true
irb(main):002:0> x ||= "ruby"
=> true
irb(main):003:0> x
=> true
And, likewise, with nil:

irb(main):014:0> x = nil
=> nil
irb(main):015:0> x ||= "ruby"
=> "ruby"
irb(main):016:0> x
=> "ruby"
However, I do not understand this behavior:

irb(main):019:0> x = false
=> false
irb(main):020:0> x ||= "ruby"
=> "ruby"
irb(main):021:0> x
=> "ruby"


We know that false != nil, and yet the ||= will assign if the left hand side
is false?

regards,
Matt

The operators are working with a three-valued logic. In a comparison,
a FalseClass object or a NilClass object will be logistically false.

Todd
 
P

Paul Mucur

It was my understanding that the ||= assignment operator assigned
the value
on the right-hand side if and only if the left hand side did not
already
have a value:

It's probably better to think of x ||= "ruby" as being short hand for
x = x || "ruby" (which it effectively is) instead of "assign if not
already having a value". If you do this, then you can see why your
last example behaves like it does.

Don't forget that there are other similar operators like +=, -= and
&&= and they all follow the same pattern:

x ?= y
x = x ? y

Where ? is one of -, +, && or ||.

Hope that this helps.
 
B

Belorion

[Note: parts of this message were removed to make it a legal post.]

Thanks all, that clears up my understanding. I hadn't thought of it as x =
x || "ruby" (even thoughI knew that x += 1 is the same as x = x + 1) and
that in that expanded case, x evaluates, logistically, to false if nil or
false.

Matt
 
T

Todd Benson

Thanks all, that clears up my understanding. I hadn't thought of it as x =
x || "ruby" (even thoughI knew that x += 1 is the same as x = x + 1) and
that in that expanded case, x evaluates, logistically, to false if nil or
false.

For conditional expressions. Here's something that may catch you off guard...

irb(main):001:0> nil || false
=> false
irb(main):001:0> false || nil
=> nil

Todd
 
C

Chris Shea

It's probably better to think of x ||= "ruby" as being short hand for
x = x || "ruby" (which it effectively is) instead of "assign if not
already having a value". If you do this, then you can see why your
last example behaves like it does.

Don't forget that there are other similar operators like +=, -= and
&&= and they all follow the same pattern:

x ?= y
x = x ? y

Where ? is one of -, +, && or ||.

Hope that this helps.

It's better to think of x ||= "ruby" as x || x = "ruby".

--
h = Hash.new('default value')

h[:test] ||= 'assigned value'
h # => {}

# same as:
h[:test] || h[:test] = 'assigned value'
h # => {}

# not:
h[:test] = h[:test] || 'assigned value'
h # => {:test=>"default value"}
 
P

Peña, Botp

From: Chris Shea [mailto:[email protected]]=20
# It's better to think of x ||=3D "ruby" as x || x =3D "ruby".
#=20
# --
# h =3D Hash.new('default value')
#=20
# h[:test] ||=3D 'assigned value'
# h # =3D> {}
#=20
# # same as:
# h[:test] || h[:test] =3D 'assigned value'
# h # =3D> {}
#=20
# # not:
# h[:test] =3D h[:test] || 'assigned value'
# h # =3D> {:test=3D>"default value"}


i'd say that's a bug in design (unless x+=3D1 is now x+x=3D1 ;)

kind regards -botp
 
A

Avdi Grimm

On this topic, am I the only one who would really like to see a
dedicated "defaulting operator" in Ruby? Something that actually has
the semantics which a lot of people mistakenly assume for ||=, i.e.
"assign if and only if nil"? I think Perl6 uses the //= operator for
this. This is such a subtle gotcha. I've seen a lot of code that was
potentially buggy because it didn't account for the fact that the
'false' value would be handled incorrectly.
 
S

s.ross

On this topic, am I the only one who would really like to see a
dedicated "defaulting operator" in Ruby? Something that actually has
the semantics which a lot of people mistakenly assume for ||=, i.e.
"assign if and only if nil"? I think Perl6 uses the //= operator for
this. This is such a subtle gotcha. I've seen a lot of code that was
potentially buggy because it didn't account for the fact that the
'false' value would be handled incorrectly.

This would be great, but you would need both a // and //= operator.
Consider this:

@my_web_page_title = @page_name || "d'oh! you forgot to name your page"

You would want to rewrite this as:

@my_web_page_title = @page_name // "d'oh! you forgot to name your page"

It would seem that adding these operators has a low potential to
introduce bugs into working code and, as you point out, there are
subtleties WRT the ||= idiom. Moving it from idiom to first-class
operator might clarify those subtleties.
 
J

Joshua Ballanco

From: Chris Shea [mailto:[email protected]]
# It's better to think of x ||= "ruby" as x || x = "ruby".
#
# --
# h = Hash.new('default value')
#
# h[:test] ||= 'assigned value'
# h # => {}
#
# # same as:
# h[:test] || h[:test] = 'assigned value'
# h # => {}
#
# # not:
# h[:test] = h[:test] || 'assigned value'
# h # => {:test=>"default value"}


i'd say that's a bug in design (unless x+=1 is now x+x=1 ;)

kind regards -botp

Not at all. Rather, this is just a subtle misunderstanding of how hash
is implemented. Consider the following:
h = Hash.new => {}
h[:test] ||= 'testing without default' => "testing without default"
h => {:test=>"testing without default"}
h = Hash.new('default value') => {}
h[:test] ||= 'testing with default' => "default value"
h
=> {}

The only reason that Chris' example behaves like "x || x = stuff" is
because he's defined a default value for the hash. If you set a default
value, than you'll never have a keyed value be empty (i.e. nil).
Consider further:
h = Hash.new => {}
puts 'empty' unless h[:test]
empty
=> nil
h = Hash.new('default value') => {}
puts 'empty' unless h[:test] => nil

I think the confusion is that, in Chris' example, there's no assignment,
so the hash only holds the default value temporarily (i.e. just long
enough to not evaluate to nil or false).
 
Y

Yossef Mendelssohn

From: Chris Shea [mailto:[email protected]]
# It's better to think of x ||=3D "ruby" as x || x =3D "ruby".
#
# --
# h =3D Hash.new('default value')
#
# h[:test] ||=3D 'assigned value'
# h # =3D> {}
#
# # same as:
# h[:test] || h[:test] =3D 'assigned value'
# h # =3D> {}
#
# # not:
# h[:test] =3D h[:test] || 'assigned value'
# h # =3D> {:test=3D>"default value"}
i'd say that's a bug in design (unless x+=3D1 is now x+x=3D1 ;)
kind regards -botp

Not at all. Rather, this is just a subtle misunderstanding of how hash
is implemented.

The only reason that Chris' example behaves like "x || x =3D stuff" is
because he's defined a default value for the hash. If you set a default
value, than you'll never have a keyed value be empty (i.e. nil).
Consider further:

I think the confusion is that, in Chris' example, there's no assignment,
so the hash only holds the default value temporarily (i.e. just long
enough to not evaluate to nil or false).

There was a long thread about this some time ago, and I'm too tired to
search for it now. In the end, it came down a difference of opinion.

Some people say this violates their expectations because they're told
that `var op=3D value` translates to `var =3D var op value` always.

Other people say this is expected because `var ||=3D value` really means
`var =3D value unless var` and everybody should know that.

Personally, I am in the former camp. I think of the latter as an
optimization that works in many places, but optimizations are usually
a form of cheating and cheaters eventually get caught. In Ruby, this
cheater gets caught in the web of deceit centered around hashes with
default values.

Note that `hash_with_default_value[key] ||=3D val` is the only place
where this is even an issue.
 
D

David A. Black

Hi --

On this topic, am I the only one who would really like to see a
dedicated "defaulting operator" in Ruby? Something that actually has
the semantics which a lot of people mistakenly assume for ||=, i.e.
"assign if and only if nil"? I think Perl6 uses the //= operator for
this. This is such a subtle gotcha. I've seen a lot of code that was
potentially buggy because it didn't account for the fact that the
'false' value would be handled incorrectly.

The false value isn't handled incorrectly, though. The test is for
boolean falseness, and both nil and false always pass that test. If
you need x to be a certain value (nil or any other), you should test
for exactly that value.

Also, it's not exactly that the lhs of ||= is nil. ||= will allow
uninitialized local variables, which are not nil:

irb(main):003:0> a ||= 1
=> 1
irb(main):004:0> b || b = 1
NameError: undefined local variable or method `b' for main:Object
from (irb):4

So we'd be getting into a thing with "uninitialized or nil, but not
false" which seems very use-case specific to me. I'd rather let the
boolean significance just do its thing.


David

--
Rails training from David A. Black and Ruby Power and Light:
ADVANCING WITH RAILS April 14-17 New York City
INTRO TO RAILS June 9-12 Berlin
ADVANCING WITH RAILS June 16-19 Berlin
See http://www.rubypal.com for details and updates!
 
R

Robert Dober

From: Joshua Ballanco [mailto:[email protected]]
# Not at all. Rather, this is just a subtle misunderstanding of

# how hash is implemented. Consider the following:
# >> h =3D Hash.new
# =3D> {}
# >> h[:test] ||=3D 'testing without default'
# =3D> "testing without default"
# >> h
# =3D> {:test=3D>"testing without default"}
# >> h =3D Hash.new('default value')
# =3D> {}
# >> h[:test] ||=3D 'testing with default'
# =3D> "default value"
# >> h
# =3D> {}
# The only reason that Chris' example behaves like "x || x =3D stuff" is
# because he's defined a default value for the hash. If you set
# a default value, than you'll never have a keyed value be empty
# (i.e. nil). Consider further:
# >> h =3D Hash.new
# =3D> {}
# >> puts 'empty' unless h[:test]
# empty
# =3D> nil
# >> h =3D Hash.new('default value')
# =3D> {}
# >> puts 'empty' unless h[:test]
# =3D> nil

my question is simple,

given,


h =3D Hash.new('default value')
#=3D> {}

why is

h[:test] ||=3D 'this is alternative'
#=3D> "default value"
h
#=3D> {}

different from

h[:test] =3D h[:test] || 'this is alternative'
#=3D> "default value"
h
#=3D> {:test=3D>"default value"}

??

That is not a nice surprise, imho; and the (syntax) sugar is not sweet := )

kind regards -botp
I respectfully disagree, this is very well defined behavior, I
partially blame the list which *very* often explained to newbies that

@x ||=3D value

assignes value to @x when it was not defined before. It is sad that
things are incorrectly explained, that is all.
Furthermore things are simple

(1) lval ||=3D rval is lval =3D lval || rval
(2) nil || x =3D x
(3) false || x =3D x
(4) non initialized ivars evaluate to nil

once one accepts these basic rules of the language there is no surprise.
I like the syntactic sugar but to use a recently used quote "DE
GVSTIBVS NON DISPVTANDVM EST"

Cheers
Robert


--=20
http://ruby-smalltalk.blogspot.com/
 
D

David A. Black

Hi --

Furthermore things are simple

(1) lval ||= rval is lval = lval || rval

I think you mean:

lval || lval = rval

(See the hash default case which flushes this out.)


David

--
Rails training from David A. Black and Ruby Power and Light:
ADVANCING WITH RAILS April 14-17 New York City
INTRO TO RAILS June 9-12 Berlin
ADVANCING WITH RAILS June 16-19 Berlin
See http://www.rubypal.com for details and updates!
 
R

Robert Dober

Hi --




I think you mean:

lval || lval = rval

(See the hash default case which flushes this out.)
Wow David, as they say "A little knowledge is a dangerous thing", well
as I said ignorants like me teaching nonsense :(

Thx for correcting me David.

Cheers
Robert
 
A

Avdi Grimm

The false value isn't handled incorrectly, though.

When I wrote "handled incorrectly", I didn't mean that Ruby handles it
incorrectly; I meant that I've seen a great deal of code people had
written which was incorrect in the face of explicit 'false' values
because of this very common misunderstanding of ||=.
The test is for
boolean falseness, and both nil and false always pass that test. If
you need x to be a certain value (nil or any other), you should test
for exactly that value.

I agree. Which is why I often use something like
if x.nil? then x = y end
which isn't nearly as concise and expressive as x ||= y. It could be
somewhat shortened to:
x.nil? && x = y
but I don't find that particularly readable. In any case, *neither*
of them conveniently handles the case where x is undefined as well as
the case where x is nil, the way ||= does, as you note below:
Also, it's not exactly that the lhs of ||= is nil. ||= will allow
uninitialized local variables, which are not nil: [...]
So we'd be getting into a thing with "uninitialized or nil, but not
false" which seems very use-case specific to me.

The core of my argument is that this idiom isn't rare or use-case
specific at all. is, in my experience of both writing and reading Ruby
code, an *extremely* common pattern. Being able to concisely and
unambiguously say "after this line, x (or h[:x]) is guaranteed to be
initialized and non-nil, either by previous assignment or by the
specified default" is something I see a need for every working day.
And because it's such a common need, and because ||= seems so
deceptively close to fulfilling it, I have seen many, many examples of
code that was subtly incorrect because it used ||=. Ruby is a
language that often offers sugar for common idioms, and this is a very
common idiom.
 
R

Robert Klemme

Note though that "logistical" != "logical" - two quite different pairs
of shoes. :)

Cheers

robert
 
B

Brian Adkins

Hi --




I think you mean:

lval || lval = rval

Huh?

My understanding has always been that x ||= y is shorthand for x = x
|| y just as x += y is shorthand for x = x + y. That also seems to be
the understanding of the "Programming Ruby" text (p. 125 of the
latest). In other words, an assignment will take place even if it's
superfluous.
 
S

Stefano Crocco

Huh?

My understanding has always been that x ||= y is shorthand for x = x

|| y just as x += y is shorthand for x = x + y. That also seems to be

the understanding of the "Programming Ruby" text (p. 125 of the
latest). In other words, an assignment will take place even if it's
superfluous.

I also thought so, but, according to "The ruby programming language", it's an
exception. It states that, if the left hand of var ||= something is not nil or
false, no assignment is performed and, if the left hand is an array element or
attribute, the setter method is not called.

Stefano+
 

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

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top