simple class question

T

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?

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
<< (e-mail address removed) >> (email)
<< TomCloyd.com >> (website)
<< sleightmind.wordpress.com >> (mental health weblog)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
R

Robert Klemme

2009/2/11 Tom Cloyd said:
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
 
T

Tom Cloyd

Robert said:
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.



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.



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.



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.



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
<< (e-mail address removed) >> (email)
<< TomCloyd.com >> (website)
<< sleightmind.wordpress.com >> (mental health weblog)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
7

7stud --

Tom said:
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
 
T

Tom Cloyd

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



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.



In ruby, a method returns the last expression that was executed in the
method or what is explicitly specified in a return statement.




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.




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
<< (e-mail address removed) >> (email)
<< TomCloyd.com >> (website)
<< sleightmind.wordpress.com >> (mental health weblog)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
7

7stud --

Tom said:
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
 
B

Brian Candler

Tom said:
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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,013
Latest member
KatriceSwa

Latest Threads

Top