[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

simple class question

Tom Cloyd

2/11/2009 8:17:00 AM

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 Cloyd, MS MA, LMHC - Private practice Psychotherapist
Bellingham, Washington, U.S.A: (360) 920-1226
<< tc@tomcloyd.com >> (email)
<< TomCloyd.com >> (website)
<< sleightmind.wordpress.com >> (mental health weblog)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


6 Answers

Robert Klemme

2/11/2009 10:01:00 AM

0

2009/2/11 Tom Cloyd <tomcloyd@comcast.net>:
> 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

--
remember.guy do |as, often| as.you_can - without end

Tom Cloyd

2/11/2009 10:37:00 AM

0

Robert Klemme wrote:
> 2009/2/11 Tom Cloyd <tomcloyd@comcast.net>:
>
>> 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
>
>
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 Cloyd, MS MA, LMHC - Private practice Psychotherapist
Bellingham, Washington, U.S.A: (360) 920-1226
<< tc@tomcloyd.com >> (email)
<< TomCloyd.com >> (website)
<< sleightmind.wordpress.com >> (mental health weblog)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


7stud --

2/11/2009 10:41:00 AM

0

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

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

Tom Cloyd

2/11/2009 10:50:00 AM

0

7stud -- wrote:
> Tom Cloyd 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
>
>
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 Cloyd, MS MA, LMHC - Private practice Psychotherapist
Bellingham, Washington, U.S.A: (360) 920-1226
<< tc@tomcloyd.com >> (email)
<< TomCloyd.com >> (website)
<< sleightmind.wordpress.com >> (mental health weblog)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


7stud --

2/11/2009 11:06:00 AM

0

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


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

Brian Candler

2/11/2009 2:44:00 PM

0

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