Velocity Reviews

Velocity Reviews (http://www.velocityreviews.com/forums/index.php)
-   Ruby (http://www.velocityreviews.com/forums/f66-ruby.html)
-   -   Writing proper getter in a Ruby way (http://www.velocityreviews.com/forums/t861380-writing-proper-getter-in-a-ruby-way.html)

Benoit Daloze 01-23-2010 05:36 PM

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 ? :D

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 ;)

Regards,
B.D.


Marnen Laibow-Koser 01-24-2010 03:04 AM

Re: Writing proper getter in a Ruby way
 
Benoit Daloze 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
marnen@marnen.org
--
Posted via http://www.ruby-forum.com/.


Benoit Daloze 01-24-2010 02:11 PM

Re: Writing proper getter in a Ruby way
 
2010/1/24 Marnen Laibow-Koser <marnen@marnen.org>:
> This seems a strange use case, but in any event, ou should not be doing
> this much class checking. =A0I suggest a different approach entirely: pas=

s
> a hash to the constructor rather like many Rails functions do.
>
> So:
>
> class Variable
> =A0attr.accessor :name, :proc
> =A0attr.writer :value
>
> =A0def initialize(options, &proc)
> =A0 =A0@name =3D options[:name] ? options[:name].to_s : nil
> =A0 =A0@value =3D options[:value]
> =A0 =A0@proc =3D proc
> =A0end
>
>>
>> =A0 def value
>> =A0 =A0 @value or @proc && @proc.call.value
>> =A0 end
>>
>> =A0 def to_s
>> =A0 =A0 name || 'unnamed_variable'
>> =A0 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 =3D options.values_at(:name, :value) # values_at is really
nice here :)

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 =3D var :x, 3 # with my way
x =3D var name: :x, value: 3 # with hash, in 1.9
x =3D var :name =3D> :x, :value =3D> 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.


The Higgs bozo 01-24-2010 02:36 PM

Re: Writing proper getter in a Ruby way
 

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.
--
Posted via http://www.ruby-forum.com/.


Benoit Daloze 01-24-2010 03:51 PM

Re: Writing proper getter in a Ruby way
 
[Note: parts of this message were removed to make it a legal post.]

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

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

Cool way to do that :) 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 :) (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 :)


Marnen Laibow-Koser 01-24-2010 04:14 PM

Re: Writing proper getter in a Ruby way
 
Benoit Daloze wrote:
> 2010/1/24 Marnen Laibow-Koser <marnen@marnen.org>:
>> �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 :)
>
> 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
marnen@marnen.org
--
Posted via http://www.ruby-forum.com/.


The Higgs bozo 01-24-2010 05:40 PM

Re: Writing proper getter in a Ruby way
 

> 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>

--
Posted via http://www.ruby-forum.com/.


The Higgs bozo 01-24-2010 06:50 PM

Re: Writing proper getter in a Ruby way
 
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)
--
Posted via http://www.ruby-forum.com/.


Benoit Daloze 01-24-2010 07:02 PM

Re: Writing proper getter in a Ruby way
 
[Note: parts of this message were removed to make it a legal post.]

>
> 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)
>


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.



All times are GMT. The time now is 02:00 PM.

Powered by vBulletin®. Copyright ©2000 - 2014, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.