From: Robert Klemme Date: 2009-02-11T19:01:04+09:00 Subject: Re: simple class question 2009/2/11 Tom Cloyd : > 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 => # 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 -- remember.guy do |as, often| as.you_can - without end