In SO question 2068165 one answer raised the ide开发者_StackOverflow社区a of using something like this:
params[:task][:completed_at] &&= Time.parse(params[:task][:completed_at])
as a DRYer way of saying
params[:task][:completed_at] = Time.parse(params[:task][:completed_at]) if params[:task][:completed_at]
where the params Hash would be coming from a (Rails/ActionView) form.
It's a kind of corollary to the well-known ||=
idiom, setting the value if the LHS is not nil/false.
Is using &&=
like this actually a recognised Ruby idiom that I've somehow missed or have I just forgotten a more commonly-used idiom? It is getting rather late...
It ought to be. If nothing else, params[:task]
is only evaluated once when using the &&=
form.
To clarify:
params[:task][:completed_at] = params[:task][:completed_at] && ...
calls [](:task)
on params
twice, [](:completed_at)
and []=(:completed_at)
once each on params[:task]
.
params[:task][:completed_at] &&= ...
calls [](:task)
on params
once, and its value is stashed away for both the [](:completed_at)
and []=(:completed_at)
calls.
Actual example describing what I'm trying to illustrate (based on Marc-Andre's example code; much thanks):
class X
def get
puts "get"
@hash ||= {}
end
end
irb(main):008:0> x = X.new
=> #<X:0x7f43c496b130>
irb(main):009:0> x.get
get
=> {}
irb(main):010:0> x.get[:foo] = 'foo'
get
=> "foo"
irb(main):011:0> x.get[:foo]
get
=> "foo"
irb(main):012:0> x.get[:foo] &&= 'bar'
get
=> "bar"
irb(main):013:0> x.get[:foo] = x.get[:foo] && 'bar'
get
get
=> "bar"
Note that using the "expanded" form causes "get" to be printed out twice, but using the compact form causes it to only be printed once.
Using &&=
, in the case of LHS is false, it is only being read once, but not being set. This should make it clearer ...
class Test
def initialize(value)
@v = value
end
def v=(value)
puts "set"
@v = value
end
def v
puts "get=>#{@v}"
@v
end
end
t = Test.new(true)
t.v = t.v && true
puts '----'
t.v &&= true
puts '----'
t = Test.new(false) # lets make LHS false
t.v = t.v && true
puts '----'
t = Test.new(false) # lets make LHS false
t.v &&= true
The result:
get=>true
set
----
get=>true
set
----
get=>false
set
----
get=>false
精彩评论