DRY ruby idiom

S

stevetuckner

I often use the ||= idiom in ruby for late initialization. But was
recently looking for an idiom that would allow me to overwrite a
variable if the right side is defined without repeating myself.

I have used the following:

var = val if val

but I have to repeat val and that violates DRY. I thought maybe &&=
would work, but did not

var &&= val

That only works if var and val are non-nil.

Any ideas out there?

Steve Tuckner
 
D

David A. Black

Hi --

I often use the ||= idiom in ruby for late initialization. But was
recently looking for an idiom that would allow me to overwrite a
variable if the right side is defined without repeating myself.

I have used the following:

var = val if val

but I have to repeat val and that violates DRY.

$ ruby -e 'a = b if b'
-e:1: undefined local variable or method `b' for main:Object
(NameError)

I thought maybe &&= would work, but did not

var &&= val

That only works if var and val are non-nil.

Any ideas out there?

I'm not sure it's possible, but it might depend on what state you want
to leave things in under what conditions. Even if you do this:

var = val if defined?(val)

var ends up being nil if val is *not* defined -- so that's
indistinguishable from the case where val exists and is nil. If it's
OK for var to be nil, then that would be OK, otherwise not.


David
 
F

Florian Gross

stevetuckner said:
I often use the ||= idiom in ruby for late initialization. But was
recently looking for an idiom that would allow me to overwrite a
variable if the right side is defined without repeating myself.

I have used the following:

var = val if val

but I have to repeat val and that violates DRY. [...]
Any ideas out there?

require 'binding_of_caller'

def and_set(name, value)
return if value.nil?
Binding.of_caller do |context|
eval("#{name} = ObjectSpace._id2ref(#{value.id})", context)
end
end

and_set:)var, val)

Not sure if you were looking for this kind of solution though. :)

Regards,
Florian Gross


begin
require 'simplecc'
rescue LoadError
def Continuation.create(*args, &block)
cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
result ||= args
return *[cc, *result]
end
end

# This method returns the binding of the method that called your
# method. It will raise an Exception when you're not inside a method.
#
# It's used like this:
# def inc_counter
# Binding.of_caller do |binding|
# eval("counter += 1", binding)
# end
# end
# counter = 0
# 2.times { inc_counter }
# counter # => 2
#
# You will have to put the whole rest of your method into the
# block that you pass into this method. If you don't do this
# an Exception will be raised. Because of the way that this is
# implemented it has to be done this way. If you don't do it
# like this it will raise an Exception.
def Binding.of_caller(&block)
old_critical = Thread.critical
Thread.critical = true
count = 0
cc, result, error, extra_data = Continuation.create(nil, nil)
error.call if error

tracer = lambda do |*args|
type, context, extra_data = args[0], args[4], args
if type == "return"
count += 1
# First this method and then calling one will return --
# the trace event of the second event gets the context
# of the method which called the method that called this
# method.
if count == 2
# It would be nice if we could restore the trace_func
# that was set before we swapped in our own one, but
# this is impossible without overloading set_trace_func
# in current Ruby.
set_trace_func(nil)
cc.call(eval("binding", context), nil, extra_data)
end
elsif type != "line"
set_trace_func(nil)
error_msg = "Binding.of_caller used in non-method context or " +
"trailing statements of method using it aren't in the block."
cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil)
end
end

unless result
set_trace_func(tracer)
return nil
else
Thread.critical = old_critical
case block.arity
when 1 then yield(result)
else yield(result, extra_data)
end
end
end
 
D

David A. Black

On Wed, 29 Sep 2004, David A. Black wrote:

A sentence disappeared: I meant to say:

Doesn't that not work?

followed by example:
$ ruby -e 'a = b if b'
-e:1: undefined local variable or method `b' for main:Object
(NameError)

:)


David
 
T

trans. (T. Onoma)

I often use the ||= idiom in ruby for late initialization. But was
recently looking for an idiom that would allow me to overwrite a
variable if the right side is defined without repeating myself.

I have used the following:

var = val if val

but I have to repeat val and that violates DRY. I thought maybe &&=
would work, but did not

var &&= val

That only works if var and val are non-nil.

Any ideas out there?

Well, callcc won't rollback vars. I tried:

val = 10
callcc {|c| (var=val) ? () : c.call }

Which might have worked in future Ruby, but alas no. But then what about
simple transactions? Isn't there a lib for something like:

transaction:)var) do
(var = val) ? commit : rollback
end

Just some wild guesses.

T.
 
S

Steve Tuckner

Florian said:
require 'binding_of_caller'

def and_set(name, value)
return if value.nil?
Binding.of_caller do |context|
eval("#{name} = ObjectSpace._id2ref(#{value.id})", context)
end
end

and_set:)var, val)

Not sure if you were looking for this kind of solution though. :)
It probably does the trick (though I can't understand the
binding_of_caller without more study), but was quite a bit more
heavyweight than I was looking for. Thanks for trying... ;-)

Steve Tuckner
 
S

Steve Tuckner

David said:
$ ruby -e 'a = b if b'
-e:1: undefined local variable or method `b' for main:Object
(NameError)
Perhaps I should clarify how I was using this. Lets say I have a
variable with a default value and I want to overwrite it with something
else as long as it is non-nil.

a = "default"
a = foo.value if foo.value

I just wanted to see if there was a simple way to avoid the double
foo.value that I was missing. Thanks for all your responses.

Steve Tuckner
 
G

Gavin Sinclair

Steve said:
a = "default"
a = foo.value if foo.value

I just wanted to see if there was a simple way to avoid the double
foo.value that I was missing. Thanks for all your responses.

Ah, well _that's_ easy:

a = foo.value || "default"

Gavin
 
M

Markus

Ah, well _that's_ easy:

a = foo.value || "default"

Gavin

I was reading up this thread (I've been in meetings & working for
the past 8 hrs or so, and thus neglecting my duties as a netizen)
wondering if anyone had suggested this yet. Will I be the one to post
it? Will I?
*sigh*

I will not that (in the original poster's terms), this is

var = val || var

which still repeats (but the computationally cheap var instead of the
presumably costly val). There may be an idiom that avoids even that,
but I'll be darned if I know what it is.

-- MarkusQ

P.S. In Icon (IIRC) you can write either

var \= val

or

var /= val

one of which effectively does

var ||= val

and the other

var = val || var

but the semantics of icon are sufficiently different from ruby to make
this only an approximate analogy.
 
B

Brian Candler

I will not that (in the original poster's terms), this is
var = val || var

or
a = foo.value || a

which more clearly shows the value (!) of this construct. Smart.

Thinking about my own code, I tend to write

var = val unless val.nil?

which is even more verbose, but seems a bit clearer, and works for the case
where val=false. However I end up assigning to a temporary variable 'val' in
the case of a complex expression. Maybe I'll adopt your pattern instead,
although I think it would leave some people scratching their heads at first.

There is the more general case of

if some.complex.expression
... do something with some.complex.expression
end

which I typically write as

if (val = some.complex.expression)
... do something with val
end

This does suggest some other solutions:

a = tmp if tmp = foo.value ## fails - first tmp is treated as method
if tmp = foo.value; a = tmp; end
(tmp = foo.value) && (a = tmp)

Icky though. It might be nice if the value of the condition evaluated in an
'if', or the LHS of && or ?:, were somehow available to the body/RHS. I
can't think how it would work nicely:

foo.value && a = $# #yuk

Regards,

Brian.
 

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
473,769
Messages
2,569,580
Members
45,053
Latest member
BrodieSola

Latest Threads

Top