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:[email protected]/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.

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs