Forum: Ruby simple class question

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Tom C. (Guest)
on 2009-02-11 10:19
(Received via mailing list)
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?

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.

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.

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

As always, much thanks for all contributions!

Tom


--

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tom C., MS MA, LMHC - Private practice Psychotherapist
Bellingham, Washington, U.S.A: (360) 920-1226
<< removed_email_address@domain.invalid >> (email)
<< TomCloyd.com >> (website)
<< sleightmind.wordpress.com >> (mental health weblog)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Robert K. (Guest)
on 2009-02-11 12:04
(Received via mailing list)
2009/2/11 Tom C. <removed_email_address@domain.invalid>:
>
>
> debugger
> 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
Tom C. (Guest)
on 2009-02-11 12:38
(Received via mailing list)
Robert K. wrote:
>> === begin code
>>  return 99
>>
>>
> method.  This is a safety feature to not allow your expectations to
> irb(main):004:0> o.send :foo=, 400
>>
> irb(main):015:0> find %w{foo bar baz}, "bar"
> Here I'm using "return" to terminate the method early.
> setting, getting and performing work. But there is no law which
>> back the results.
>
>         tf.cleanup
> end
>
>   end
>   end
> (all this untested)
>
> Hopefully my remarks were helpful...
>
> Kind regards
>
> robert
>
>
God help me. I had NO idea about this "#send" business. None. Everything
look different now.

As for your other remarks, I don't know how to thank you for the labor
of conveying your teaching to me in such detail. You gave me back a
great deal for my questions. I qill try to make good use of it. I
actually AM building a small hash based cyclic graph database with my
present coding efforts. It's going quite well, and I have immediate need
for it, so all this chatter isn't just programming theory for me. I'd be
getting work done with it right now if it were completely working.

Thanks again!

Tom

--

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tom C., MS MA, LMHC - Private practice Psychotherapist
Bellingham, Washington, U.S.A: (360) 920-1226
<< removed_email_address@domain.invalid >> (email)
<< TomCloyd.com >> (website)
<< sleightmind.wordpress.com >> (mental health weblog)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7stud -. (Guest)
on 2009-02-11 12:42
Tom C. wrote:
> class Test
>
>   def t1=(val)
>   @val = val
>   return 99
>   end
>
>   def t2=(val)
>   @val = val * 2
>   return 99
>   end
>
> end

> I enter at the command line:
>
> (rdb:1) p test.t2 = 2
> 2
>
> I should have gotten 4.

I think you should have got 99.  You called the method t2=(), and t2=()
returns 99.

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

The problem appears to be that ruby does not respect a return statement
in a setter method.  Instead, ruby appears to return the value of the
argument to the setter method--which is not necessarily even the value
assigned to the instance variable:

class Test

  def t1=(val)
    @val = val * 2
    return 99
  end

  def t1
    @val
  end

end

puts t.t1 = 2    #2  (not 99, and not 4!)
puts t.t1        #4  (as expected)

Normally, a return statement at the end of a method would be the value
returned to the method call:

class Test

  def t1=(val)
    @val = val * 2
  end
  def t1
    @val
  end

  def a
    @val = "goodbye"
    return 99
  end

end

t = Test.new
puts t.t1 = "hello"  #hello  (the arg to the setter method)
puts t.t1            #hellohello  (the actual value of @val)
puts t.a             #99 (the return value of the method)
puts t.t1            #goodbye (as expected)

You can test on your own that ruby does respect a return statement in a
getter method.

> 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 ruby, a method returns the last expression that was executed in the
method or what is explicitly specified in a return statement.


>
> 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 depends on what you want to do.  If you simply want to set and get an
instance variable's value, using attr_accesor saves you from having to
manually type out the setter and getter methods.  However, if you want
your  setter or getter method to transform the value of the argument in
some way, then you have to define your own getter and setter methods.


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

Here is an example of what you can do:

class MyDataProcessor
  attr_reader :file

  def initialize(fname)
    @file = File.open(fname)
  end

  def file=(fname)
    @file = File.open(fname)
  end

  def get_data
    data = @file.gets

    #process data:
    data.chomp.split if data #data=nil at EOF
  end

end


dp = MyDataProcessor.new("data.txt")

while data = dp.get_data
  p data  #do something with processed data
end

dp.file.close
Tom C. (Guest)
on 2009-02-11 12:51
(Received via mailing list)
7stud -- wrote:
>>   @val = val * 2
>> 2
>> test.t2 to do this simple thing I'm asking of it?
>   def t1=(val)
> puts t.t1 = 2    #2  (not 99, and not 4!)
>   def t1
> t = Test.new
>> explain it. I've been reading about "return" and I cannot account for
>> Finally, is this general approach I'm taking the best or usual way to
> some way, then you have to define your own getter and setter methods.
>> will arrive soon.
>
>
>
>
Thanks much for your thoughts. You added some material I didn't see (or
haven't yet understood!) in Robert's response. I can see you were as
surprised (more or less) as I was by the failure of the "return n"
statement to do anything. I never could get it to respond as I had
expected.

I will study your example. I'm sure it will teach me a few things.
Thanks!

~t.

--

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tom C., MS MA, LMHC - Private practice Psychotherapist
Bellingham, Washington, U.S.A: (360) 920-1226
<< removed_email_address@domain.invalid >> (email)
<< TomCloyd.com >> (website)
<< sleightmind.wordpress.com >> (mental health weblog)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7stud -. (Guest)
on 2009-02-11 13:06
Tom C. wrote:
> I can see you were as
> surprised (more or less) as I was by the failure of the "return n"
> statement to do anything.
>

I'm a ruby beginner too.

>
> I will study your example. I'm sure it will teach me a few things.
> Thanks!
>

Here is a little fancier get_data method:

  def get_data
    while data = @file.gets
      yield data.chomp.split
    end

    @file.close
  end

A yield statement will send the specified value to a block that you
specify to the right of the method call:

dp = MyDataProcessor.new("data.txt")

dp.get_data {|processed_data| p processed_data}

If you need to execute more than one line of code in that block, then
you would write it like this:

dp.get_data do |processed_data|
  #some code here
  #other code here

  p processed_data
end
Brian C. (Guest)
on 2009-02-11 16:44
Tom C. wrote:
> 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?

Just to make it clear, although you probably realise by now: you're not
calling test.t2, you're calling test.t2=

The name of the method really does contain the = sign, and is actually
the symbol :t2=

Operators are other examples of non-alphanumeric method names:

  class Test
    def +(other)
      .. do something
    end
    def [](key)
      ..
    end
    def [](key, value)
      ..
    end
  end

So you can make your own class look like a Ruby one:

  t = Test.new
  puts t + 1     # calls your :+ method
  puts t[123]    # calls your :[] method
  t[123] = 456   # calls your :[]= method

I especially like :[] - smiley programming :-)

Regards,

Brian.
This topic is locked and can not be replied to.