Tonight I'm exploring the problem of tossing a value at a class instance and
getting a value back - having it behave like a method, in other words. I've
not looked at this before, and I don't have much confidence about what I'm
doing. I have a simple example from Thomas 3rd ed., and have modified it a
bit. It's not working as I'd expect - at all.
The code:
=== begin code
# test.rb
def main
test = Test.new puts 'bye' # <=========== line 6
end
class Test
def t1=(val)
@val = val
return 99
end
def t2=(val)
@val = val * 2
return 99
end
end
%w(rubygems ruby-debug).each{ |lib| require lib }
Debugger.start
debugger
main
=== end code
I start this, and the debugger stops it. I put a break point a line 6, and
continue.
With execution stopped, I enter at the command line:
(rdb:1) p test.t2 = 2
2
I should have gotten 4. I only ever get back the same number I put in. Can
someone tell me what the problem is, and what I have to do to get test.t2 to
do this simple thing I'm asking of it?
There is no problem - other than probably that you do not yet know
that = always returns the right hand side no matter what you do in a
method. This is a safety feature to not allow your expectations to
fool you when seeing =. You can, however, see that the method works
as implemented:
irb(main):001:0> o = Object.new
=> #<Object:0x7ff9c69c>
irb(main):002:0> def o.foo=(x) p x; 123 end
=> nil
irb(main):003:0> o.foo = 300
300
=> 300
irb(main):004:0> o.send :foo=, 400
400
=> 123
As you can see, method foo= actually returns 123 as I have told her
but Ruby never let's you see it - unless you use #send.
My other question: the "return 99" business is from Thomas. He doesn't
explain it. I've been reading about "return" and I cannot account for why
it's there. It doesn't seem to do any thing. Can someone explain THAT.
In this case it's redundant because it's the last statement in the
method anyway and a method will return the value of the last
expression evaluated. In others it's not:
irb(main):011:0> def find(enum,x)
irb(main):012:1> enum.each {|e| p e; return e if x == e}
irb(main):013:1> nil
irb(main):014:1> end
=> nil
irb(main):015:0> find %w{foo bar baz}, "bar"
"foo"
"bar"
=> "bar"
irb(main):016:0> find %w{foo bar baz}, "gogo"
"foo"
"bar"
"baz"
=> nil
irb(main):017:0>
Here I'm using "return" to terminate the method early.
Finally, is this general approach I'm taking the best or usual way to send
data to a class method and get some output? Until now, I thought I had to
read an accessor variable to get output back, but I'm thinking now that that
is probably NOT the best way.
It is certainly not for assignments. And in the more general case I
believe there is a tendency to rather separate these things, i.e.
setting, getting and performing work. But there is no law which
prevents methods from returning something useful. In any case you
should be aware that methods "leak" the value of the last expression
which can have side effects that you do not intend (i.e. a caller
stores the value and then later accidentally modifies internal state
of your object). Having said that you should always care what your
methods will return - either explicitly or implicitly.
What I'm wanting to do, say, create a file object which gives me more data
when I call upon it (it'll read a record and do some processing, then pass
back the results.
I do not know the nature of your processing but to me it seems
superior design to separate processing and reading. Take CSV as an
example: you can iterate the file but do not get back lines (as in the
case of File) but you get complete records which were parsed from the
file. You could do something similar, e.g.
class TomsFile
Record = Struct.new :name, :size, :age
def initialize(io)
@io = io
end
def self.open(file)
File.open(file) do |io|
tf = TomsFile.new(io)
begin
yield tf
ensure
tf.cleanup
end
end
end
def each
@io.each do |line|
# assuming fields are separated by #
yield Record.new(*line.split(/#/))
end
end
end
def cleanup
# NOP for now
end
TomsFile.open "foo.tf" do |tf|
tf.each do |rec|
printf "Found %-10s %4d\n", rec.name, rec.size
end
end
You could then add another class, say TomsFileProcessor which gets a
TomsFile and does the processing like
class TomsFileProcessor
attr_reader :count, :sum
def initialize(tf)
raise ArgumentError unless tf.is_a? TomsFile
@tf = tf
@count = @sum = 0
end
def process(record)
@count += 1
@sum += record.size
# leakage accepted
end
def process_all
@tf.each {|rec| process(rec)}
self # rather not accidentally return @tf
end
end
And then
TomsFile.open "foo.tf" do |tf|
tfc = TomsFileProcessor.new tf
tf.process_all
printf "Summary count=%4d sum=%4d\n", tf.count, tf.sum
end
(all this untested)
Of course, you could also devise other schemes of interaction.
Normally I'd use a method, but I'm trying to become more
"classy" in my programming, hence this effort. I'm basically still
struggling to find a good reason to use a class at all. So far, it's proven
far too difficult to get something OUT of a class instance. I don't see the
point of the bother, yet. I'm hoping some enlightenment will arrive soon.
Hopefully my remarks were helpful...
Kind regards
robert
···
2009/2/11 Tom Cloyd <tomcloyd@comcast.net>:
--
remember.guy do |as, often| as.you_can - without end