DRY ruby idiom

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

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

···

On Wed, 29 Sep 2004, stevetuckner wrote:

--
David A. Black
dblack@wobblini.net

stevetuckner wrote:

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

Regards,
Florian Gross

binding_of_caller.rb (2.14 KB)

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.

···

On Tuesday 28 September 2004 04:29 pm, stevetuckner wrote:

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?

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)

:slight_smile:

David

···

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

--
David A. Black
dblack@wobblini.net

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'

···

On Wed, Sep 29, 2004 at 05:37:21AM +0900, David A. Black wrote:

On Wed, 29 Sep 2004, stevetuckner wrote:

                       ===
              a

  -e:1: undefined local variable or method `b' for main:Object
  (NameError)

--
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

Florian Gross wrote:

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

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

Steve Tuckner

David A. Black wrote:

$ 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

Hi --

···

On Wed, 29 Sep 2004, Mauricio Fernández wrote:

On Wed, Sep 29, 2004 at 05:37:21AM +0900, David A. Black wrote:
> Hi --
>
> On Wed, 29 Sep 2004, stevetuckner wrote:
>
> > 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'
                       ===
              a

Ummmm, no. Look at Steve's code again.

David

--
David A. Black
dblack@wobblini.net

Steve Tuckner wrote:

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

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.

···

On Tue, 2004-09-28 at 21:30, Gavin Sinclair wrote:

Steve Tuckner wrote:
> 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

     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.