Writing proper getter in a Ruby way

Hi Rubyists !

While writing a ‘getter’(it actually does more than just getting a
@var’s value) method, I was wondering what was the most ruby-way to
write it.

The ‘issue’ is
if the @var is not initialized in constructor, and you access it
directly like “@var”,
Ruby gives you a warning(only if you enable them of course) :
“warning: instance variable @value not initialized”

While “@var” returns nil as expected, it still show this warning.
Well, I know you would probably not care too much about warnings(do
you?), but how would you deal to make it disappear?

If you use attr_* methods, you’ll never see this warning, because it’s
“cheating” (For what I understood of the C code, it checks if
defined).

In fact, a ruby equivalent(for the user) of
attr_reader :var
is
def var
instance_variable_defined?(:@var) ? @var : nil
end
and not just “@var” (because of the warning).

I think you will agree with me, using #instance_variable_defined? is
not very nice.

Now I begin to describe the real code, because abstract examples would
be hard.
Here it is:

class Variable
attr_accessor :name, :proc
attr_writer :value

Create a new Variable, with optional name, value and proc

def initialize(*args, &proc)
args.each do |arg|
case arg
when Numeric then @value = arg
when String, Symbol then @name = arg.to_s
else raise ArgumentError, “…”
end
end
@proc = proc
end

def value
@value or @proc && @proc.call.value
end

def to_s
name || ‘unnamed_variable’
end
end

So the constructor have optional *args, and so we are not sure if a
String or Symbol will be given for the name, neither if the value will
be provided.

For #to_s, I used the accessor(getter) of @name (“name”), that avoid
me the warning(instead of @name || …)

But for value, it’s a bit complicated. Because we want to return or
the value if given, or the result of the proc.
I can’t use the accessor of value, because we are in a method with the
same name …

There are then many possibles:

  1. Initialize @value in #initialize : “@value = nil” or “@value =
    unnamed_variable” (but it looks so old school)
  2. Verify if @value is defined with #instance_variable_defined? :
    instance_variable_defined?(:@value) ? @value : @proc &&
    @proc.call.value (but it’s awful to read, btw #defined? still show the
    warning)
  3. We don’t care about warnings !
  4. bad names involve this conflict, we should rename @value (we can’t
    change the name of the method), and then use his accessor. The main
    problem is we’ll have then something like var.val = 2, which is less
    easy to understand than var.value = 2.
  5. Another idea ? :smiley:

Excuse me if I’m writing very much for a very small problem, but I
thank you already if you take time to read and/or reply :wink:

Regards,
B.D.

2010/1/24 Marnen Laibow-Koser [email protected]:

def initialize(options, &proc)

def to_s
name || ‘unnamed_variable’
end
end

…and these can stay the same.

Thank you for your answer. I know that class checking is not so OO.
The old behavior was like you propose: using a Hash. and then we were
using
@name, @value = options.values_at(:name, :value) # values_at is really
nice here :slight_smile:

That’s a very cool approach except it’s really longer and I think it’s
significant in this context.
We are writing a math library, this class represent a mathematical
variable, so here we have:

x = var :x, 3 # with my way
x = var name: :x, value: 3 # with hash, in 1.9
x = var :name => :x, :value => 3 # with hash, in 1.8 (not relevant)
To specify the name is already redundant with the real var name. It’s
possible via caller to get it, with parsing, but that’s not a very
good way neither. (Would you support that?)

So much checking in a case statement for classes looks bad, but it was
intended to raise an ArgumentError if we still used the old behavior.

So, could I have your opinion, if we forget about Hash because it
looks too long here ?
Thank you for your answer again.

Benoit D. wrote:
[…]

Now I begin to describe the real code, because abstract examples would
be hard.
Here it is:

class Variable
attr_accessor :name, :proc
attr_writer :value

Create a new Variable, with optional name, value and proc

def initialize(*args, &proc)
args.each do |arg|
case arg
when Numeric then @value = arg
when String, Symbol then @name = arg.to_s
else raise ArgumentError, “…”
end
end
@proc = proc
end

This seems a strange use case, but in any event, ou should not be doing
this much class checking. I suggest a different approach entirely: pass
a hash to the constructor rather like many Rails functions do.

So:

class Variable
attr.accessor :name, :proc
attr.writer :value

def initialize(options, &proc)
@name = options[:name] ? options[:name].to_s : nil
@value = options[:value]
@proc = proc
end

def value
@value or @proc && @proc.call.value
end

def to_s
name || ‘unnamed_variable’
end
end

…and these can stay the same.

Best,

Marnen Laibow-Koser
http://www.marnen.org
[email protected]

I assume the usual thing is to assign @value = nil in initialize() and
call it a day.

But I think you are asking a higher-level question: How do we abstract
away this detail?

The attr_* methods define methods which create instance variables upon
first being called. But what if we want those instance variables to be
initialized beforehand?

We would need to override the attr_* methods. But those methods belong
to the class object–how are they going to initialize variables in the
instances? By inserting code into the initialize() ancestor chain.

module InitializeAttr
[:attr_reader, :attr_writer, :attr_accessor].each do |method|
define_method method do |*syms|
super(*syms).tap do
mod = Module.new do
define_method :initialize do |*args, &block|
super(*args, &block).tap do
syms.each do |sym|
instance_variable_set("@#{sym}", nil)
end
end
end
end
include mod
end
end
end
end

class Control
attr_writer :a, :b
def bar
@a.to_i + 5
end
end

control = Control.new
p control.instance_variables
#=> []
p control.bar
#=> warning: instance variable @a not initialized
#=> 5

class Experiment
extend InitializeAttr
attr_writer :a, :b
def bar
@a.to_i + 5
end
end

exp = Experiment.new
p exp.instance_variables
#=> [:@a, :@b]
p exp.bar
#=> 5

yay no warnings

Therein lies either an abstraction technique or an overkill technique.

A few things about define_method blocks,

  • The &block argument is unsupported in Ruby 1.8.6. Sell your soul to
    eval() for a workaround.

  • Implicit super arguments are broken in 1.8.7. It passes the regular
    arguments but forgets about the &block argument.

  • In Ruby 1.9 super must take explicit arguments. Don’t know why.

Benoit D. wrote:

2010/1/24 Marnen Laibow-Koser [email protected]:

�def initialize(options, &proc)

� def to_s
� � name || ‘unnamed_variable’
� end
end

…and these can stay the same.

Thank you for your answer. I know that class checking is not so OO.

It’s very OO. It’s just not very Rubyish.

The old behavior was like you propose: using a Hash. and then we were
using
@name, @value = options.values_at(:name, :value) # values_at is really
nice here :slight_smile:

That’s a very cool approach except it’s really longer and I think it’s
significant in this context.
We are writing a math library, this class represent a mathematical
variable,

Why do you need a separate class for this, instead of using Ruby’s
variable mechanism?

so here we have:

x = var :x, 3 # with my way
x = var name: :x, value: 3 # with hash, in 1.9
x = var :name => :x, :value => 3 # with hash, in 1.8 (not relevant)
To specify the name is already redundant with the real var name. It’s
possible via caller to get it, with parsing, but that’s not a very
good way neither. (Would you support that?)

No, that seems bad. I think you have three good choices for the
constructor, then:

  • Define the arguments positionally so that it’s always new(name, value)
  • Pass a short hash: new(:x => 3)
  • Use method_missing: x = Variable.x(3)

So much checking in a case statement for classes looks bad, but it was
intended to raise an ArgumentError if we still used the old behavior.

Then just check for a Hash!

Or better yet, support both syntaxes.

So, could I have your opinion, if we forget about Hash because it
looks too long here ?

I don’t think that’s a great reason to drop it, but see above for other
ideas.

Thank you for your answer again.

Best,

Marnen Laibow-Koser
http://www.marnen.org
[email protected]

I don’t have that:

When I said “A few things about define_method blocks,” I meant this,

class P
define_method :m do |*args, &b|
[args, b]
end
end

class C < P
define_method :m do |*args, &b|
p [args, b]
p super
end
end

C.new.m(:a, :b) { |e| e }

Ruby 1.9:
#=> in `block in class:C’: implicit argument passing of super from
method defined by define_method() is not supported. Specify all
arguments explicitly. (RuntimeError)

Ruby 1.8.7:
#=> [[:a, :b], #Proc:0x000270b0@test/iacc.rb:15]
#=> [[:a, :b], nil]

So another way, probably less ‘overkill’ you made me think is:

class Test
attr_reader :var
def initialize(*args)
if args.length > 0
@var = :value
end
end

alias :get_var :var
def var
get_var || 3
end
end

p Test.new.var #=> 3
p Test.new(:arg1, :arg2).var #=> :value

I dunno, it looks convoluted. Might as well go back to “old school”
style, or if it becomes redundant you can use the previously mentioned
technique to abstract like this,

initialized_attr_reader :var, 3

Going further, you can change the ‘3’ into a block evaluated by newly
created instances.

Or maybe Struct is all you need for initializing variables,

Struct.new(:a, :b).new(2, 3)
#=> #<struct #Class:0x90bdc a=2, b=3>
Struct.new(:a, :b).new
#=> #<struct #Class:0x85070 a=nil, b=nil>

I assume the usual thing is to assign @value = nil in initialize() and
call it a day.
That’s the first option, it just looks not cool at all, but it’s very
simple
an explicit, sure.

       end

def bar

#=> [:@a, :@b]
p exp.bar
#=> 5

yay no warnings

Therein lies either an abstraction technique or an overkill technique.
Cool way to do that :slight_smile: but overkill also…
It’s very interesting anyway how we can change that.

A few things about define_method blocks,

  • The &block argument is unsupported in Ruby 1.8.6. Sell your soul to
    eval() for a workaround.

  • Implicit super arguments are broken in 1.8.7. It passes the regular
    arguments but forgets about the &block argument.

  • In Ruby 1.9 super must take explicit arguments. Don’t know why.

I don’t have that:
($ ruby -v #=> ruby 1.9.2dev (2010-01-14 trunk 26319)
[x86_64-darwin10.2.0])
class P
def m(*args, &b)
[args, b]
end
end

class C < P
def m(*args, &b)
p [args, b]
super.tap { |sup| p sup }
end
end

C.new.m(:a, :b) { |e| e }
#=> [[:a, :b], #Proc:...]
#=> [[:a, :b], #Proc:...]
#=> [:a, :b]

So another way, probably less ‘overkill’ you made me think is:

class Test
attr_reader :var
def initialize(*args)
if args.length > 0
@var = :value
end
end

alias :get_var :var
def var
get_var || 3
end
end

p Test.new.var #=> 3
p Test.new(:arg1, :arg2).var #=> :value

Just aliasing the old accessor, too easy :slight_smile: (and short, no line lost if
we
compare to initialization, and we suppose to have already other attr_*)
I just forgot a moment name conflicts about methods don’t exist in Ruby,
because you can so easily copy(alias) the old method.

Thanks for your answer :slight_smile:

Wait, why couldn’t you just call accessors through ‘self’?

class Foo
attr_accessor :a, :b
def bar
self.a.to_i + 5
end
end

p Foo.new.bar
#=> 5

(no warnings)

p Foo.new.bar
#=> 5

(no warnings)

We are in #value, to get @value
class C
attr_reader :var
def var
self.var || 2
end
end

C.new.var #=> SystemStackError: stack level too deep

Anyway, we have chosen sth like:
args.unshift nil if Numeric === args.first
(@name, @value), @proc = args, proc

And then not using the accessor of attr_* for reading.
It’s just a quick escape sequence from initialization.

Thanks for all your answers,
Regards,
B.D.