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.
--
"Hey brother Christian with your high and mighty errand, Your actions speak
so loud, I can't hear a word you're saying."
-Greg Graffin (Bad Religion)
The operators are working with a three-valued logic. In a comparison,
a FalseClass object or a NilClass object will be logistically false.
Todd
> 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.
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
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
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"}
--
Chris
i'd say that's a bug in design (unless x+=1 is now x+x=1 ;)
kind regards -botp
--
Avdi
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.
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).
--
Posted via http://www.ruby-forum.com/.
<snip>
> 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:
<snip>
> 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).
> --
> Posted viahttp://www.ruby-forum.com/.
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= value` translates to `var = var op value` always.
Other people say this is expected because `var ||= value` really means
`var = 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] ||= val` is the only place
where this is even an issue.
--
-yossef
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!
@x ||= 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 ||= rval is lval = lval || rval
(2) nil || x = x
(3) false || x = 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
--
http://ruby-smalltalk.blogspot.com/
---
Whereof one cannot speak, thereof one must be silent.
Ludwig Wittgenstein
On Sat, 5 Apr 2008, Robert Dober wrote:
> 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.)
Thx for correcting me David.
Cheers
Robert
>
>
>
>
> 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!
>
>
--
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.
--
Avdi
--
Avdi
Note though that "logistical" != "logical" - two quite different pairs
of shoes. :-)
Cheers
robert
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+
Strictly speaking, it isn't shorthand for either, since there are
cases where the expansion will fail but ||= won't because x isn't
initialized. However, discounting that, the expansion is:
x || x = y
The only time this matters is with hashes that have default values. In
every other case, as far as I know, x = x || y also describes what's
happening. But the expansion which describes *every* case is x || x =
y.
I wrote a blog post about this recently:
http://dablog.rubypal.com/2008/3/25/a-short-circuit-edge-case
(I changed || to or for some reason, which screws up the precedence,
but I inserted corrections later.)
> The only time this matters is with hashes that have default values. In
> every other case, as far as I know, x = x || y also describes what's
> happening. But the expansion which describes *every* case is x || x =
> y.
One other case (a very similar one) in which it matters is this:
class C
def x; @x || 5; end
def x=(v); puts "assigning @x=#{v}"; @x = v; end
end
c = C.new
c.x ||= 3 # no effect
p c.x # 5
c.x = 2 # assigning @x=2
p c.x # 2
--
vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407
> The only time this matters is with hashes that have default values. In
> every other case, as far as I know, x = x || y also describes what's
> happening.
Well here's another:
class Foo
attr_writer :x
def x
@x || 1 # !> instance variable @x not initialized
end
def inspect
"Foo(#{@x})" # !> instance variable @x not initialized
end
end
f = Foo.new
f.x # => 1
f # => Foo()
f.x ||= 2
f.x # => 1
f # => Foo()
f.x || f.x = 2
f.x # => 1
f # => Foo()
f.x = f.x || 2
f.x # => 1
f # => Foo(1)
--
Rick DeNatale
My blog on Ruby
http://talklikeaduck.denhaven2.com/
c.x = c.x || a
and
c.x || c.x = a
seem to me having the same effect.
I only came up with a perverse example to show the difference
class C
count = 0
def x; true end
define_method :x= do |_|
count += 1
@x = count
end
end
c = C.new
c.x || c.x = 42
p c
c.x = c.x || 42
p c
c.x ||= 42
p c
On Sun, 6 Apr 2008, Robert Dober wrote:
> On Sat, Apr 5, 2008 at 7:57 PM, Joel VanderWerf <vj...@path.berkeley.edu> wrote:
>> David A. Black wrote:
>>
>>
>>> The only time this matters is with hashes that have default values. In
>>> every other case, as far as I know, x = x || y also describes what's
>>> happening. But the expansion which describes *every* case is x || x =
>>> y.
>>>
>>
>> One other case (a very similar one) in which it matters is this:
>>
>> class C
>> def x; @x || 5; end
>> def x=(v); puts "assigning @x=#{v}"; @x = v; end
>> end
>>
>> c = C.new
>> c.x ||= 3 # no effect
>> p c.x # 5
>> c.x = 2 # assigning @x=2
>> p c.x # 2
>>
>> --
>> vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407
>>
>>
> Hmm I am not sure I believe you rushed your example a little bit
>
> c.x = c.x || a
> and
> c.x || c.x = a
> seem to me having the same effect.
I don't think so:
irb(main):017:0> c = C.new
=> #<C:0x87d0c>
irb(main):018:0> c.x || c.x = 1 # 5 || c.x = 1 => 5
=> 5
irb(main):019:0> c.instance_eval { @x }
=> nil
irb(main):020:0> c.x = c.x || 1 # c.x = 5 || 1 => c.x = 5
assigning @x=5
=> 5
irb(main):021:0> c.instance_eval { @x }
=> 5
--
That's being a little nitpicky. I think either word works in this
case, but I understand what you meant :-)
Todd
Absolutely!
> I think either word works in this
> case, but I understand what you meant :-)
If by "works" you mean "can be understood" then, yes. If you say that
the word can be placed there from a language point of view, also yes. I
do believe though that "logistically" is semantically misplaced here. :-))
Cheers
robert
class << C = Class::new
def x; @x || 5 end
def get_x; @x end
def x= v; @x=v end
end
C.get_x --> nil
C.x ||= 42
C.get_x --> nil
C.x || C.x = 42
C.get_x --> nil
C.x = C.x || 42
C.get_x --> 5
Hurray I guess I finally got it.
Thx for the enlightenment.
Cheers
Robert
Interesting - nice blog post. That is surprising, inconsistent and
unfortunate :(