[lnkForumImage]
TotalShareware - Download Free Software

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


 

Forums >

comp.lang.ruby

A couple of questions regarding class design

Michael W. Ryder

4/20/2008 9:43:00 PM

I am part way through implementing a Rational math class to further my
understanding of Ruby and had a couple of questions that I can't find
the answer to in Pickaxe.
The first question regards creating a new instance of the class. The
initialize method expects two integer values. While I have no problem
making sure that they are integers I am not sure what to do if they are
not. Do I return nil or some other result?
The other question is how to override basic math operations like
addition and multiplication. I could implement them as x.add(y) but
would prefer to just be able to enter x + y. I think I have to create a
new instance of the class for the result and return that but am not sure
how to make it so that Ruby calls the right method when it sees x + y.
On a related note is there any good source for writing operations like
the math and probably coerce? If I can get the math working the
comparable operations should be "trivial".
35 Answers

Joel VanderWerf

4/20/2008 10:12:00 PM

0

Michael W. Ryder wrote:
> I am part way through implementing a Rational math class to further my
> understanding of Ruby and had a couple of questions that I can't find
> the answer to in Pickaxe.
> The first question regards creating a new instance of the class. The
> initialize method expects two integer values. While I have no problem
> making sure that they are integers I am not sure what to do if they are
> not. Do I return nil or some other result?

You could raise an exception:

unless ...
raise ArgumentError, "Rational arguments must be integers"
end

> The other question is how to override basic math operations like
> addition and multiplication. I could implement them as x.add(y) but
> would prefer to just be able to enter x + y. I think I have to create a

class Myclass
def +(x)
...
end
end

> new instance of the class for the result and return that but am not sure
> how to make it so that Ruby calls the right method when it sees x + y.
> On a related note is there any good source for writing operations like
> the math and probably coerce? If I can get the math working the
> comparable operations should be "trivial".

There's some explanation of how to write coerce methods in the PickAxe
(p. 358 of my 2nd ed. pdf copy).

--
vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

Robert Klemme

4/20/2008 10:15:00 PM

0

On 20.04.2008 23:43, Michael W. Ryder wrote:
> I am part way through implementing a Rational math class to further my
> understanding of Ruby and had a couple of questions that I can't find
> the answer to in Pickaxe.
> The first question regards creating a new instance of the class. The
> initialize method expects two integer values. While I have no problem
> making sure that they are integers I am not sure what to do if they are
> not. Do I return nil or some other result?

The return value of #initialize is ignored - always. The proper way to
handle this would be to raise an exception - presumably ArgumentError.

> The other question is how to override basic math operations like
> addition and multiplication. I could implement them as x.add(y) but
> would prefer to just be able to enter x + y. I think I have to create a
> new instance of the class for the result and return that but am not sure

Exactly.

> how to make it so that Ruby calls the right method when it sees x + y.

You need to implement #coerce, #+, #-, #/, #*, #+@, #-@ - that is if you
want to have full support of basic math. #coerce is a bit tricky but I
am sure there are tutorials around - I just don't have a URL handy. But
you can watch how it works:

irb(main):003:0> 1.coerce 2
=> [2, 1]
irb(main):004:0> 1.coerce 2.0
=> [2.0, 1.0]
irb(main):005:0> 1.0.coerce 2
=> [2.0, 1.0]
irb(main):006:0> 1.0.coerce 2.0
=> [2.0, 1.0]

> On a related note is there any good source for writing operations like
> the math and probably coerce? If I can get the math working the
> comparable operations should be "trivial".

For comparisons you just need to implement #<=> and then include
Comparable in your class.

And for completeness reasons you should also implement #eql?, #== and #hash.

Kind regards

robert

Michael W. Ryder

4/21/2008 6:33:00 PM

0

Joel VanderWerf wrote:
> Michael W. Ryder wrote:
>> I am part way through implementing a Rational math class to further my
>> understanding of Ruby and had a couple of questions that I can't find
>> the answer to in Pickaxe.
>> The first question regards creating a new instance of the class. The
>> initialize method expects two integer values. While I have no problem
>> making sure that they are integers I am not sure what to do if they are
>> not. Do I return nil or some other result?
>
> You could raise an exception:
>
> unless ...
> raise ArgumentError, "Rational arguments must be integers"
> end
>
That worked except for a minor problem. I first tried a.class ==
Integer and it failed. I had to use Fixnum to get it to work. I
thought that Integer being the super class for Fixnum and Bignum I could
just test for Integer without having to test for both of the subclasses.
Is there any way to do this?

>> The other question is how to override basic math operations like
>> addition and multiplication. I could implement them as x.add(y) but
>> would prefer to just be able to enter x + y. I think I have to create a
>
> class Myclass
> def +(x)
> ...
> end
> end
>

I didn't think it would be that easy, but I guess it was.


>> new instance of the class for the result and return that but am not sure
>> how to make it so that Ruby calls the right method when it sees x + y.
>> On a related note is there any good source for writing operations like
>> the math and probably coerce? If I can get the math working the
>> comparable operations should be "trivial".
>
> There's some explanation of how to write coerce methods in the PickAxe
> (p. 358 of my 2nd ed. pdf copy).
>

I found the example in my copy at the same place. I just didn't notice
it when scanning the book earlier trying to find the information.
Thanks for the assistance.

Michael W. Ryder

4/21/2008 6:48:00 PM

0

Robert Klemme wrote:
> On 20.04.2008 23:43, Michael W. Ryder wrote:
>> I am part way through implementing a Rational math class to further my
>> understanding of Ruby and had a couple of questions that I can't find
>> the answer to in Pickaxe.
>> The first question regards creating a new instance of the class. The
>> initialize method expects two integer values. While I have no problem
>> making sure that they are integers I am not sure what to do if they
>> are not. Do I return nil or some other result?
>
> The return value of #initialize is ignored - always. The proper way to
> handle this would be to raise an exception - presumably ArgumentError.
>

My background in older languages is slowing me down. I thought that
there might be a way to return a value signifying an invalid entry
without having to worry about exception handling. Haven't got that far
in Ruby yet.


>> The other question is how to override basic math operations like
>> addition and multiplication. I could implement them as x.add(y) but
>> would prefer to just be able to enter x + y. I think I have to create
>> a new instance of the class for the result and return that but am not
>> sure
>
> Exactly.
>
>> how to make it so that Ruby calls the right method when it sees x + y.
>
> You need to implement #coerce, #+, #-, #/, #*, #+@, #-@ - that is if you
> want to have full support of basic math. #coerce is a bit tricky but I
> am sure there are tutorials around - I just don't have a URL handy. But
> you can watch how it works:
>

I have noticed people using things like #+ before. Is that the same as
just + when defining a method? I created a multiplication method using
'def *(x)' and it seems to work fine. Hopefully I can use the coerce
method in the Roman numerals class in Pickaxe to create my coerce method.

> irb(main):003:0> 1.coerce 2
> => [2, 1]
> irb(main):004:0> 1.coerce 2.0
> => [2.0, 1.0]
> irb(main):005:0> 1.0.coerce 2
> => [2.0, 1.0]
> irb(main):006:0> 1.0.coerce 2.0
> => [2.0, 1.0]
>
>> On a related note is there any good source for writing operations like
>> the math and probably coerce? If I can get the math working the
>> comparable operations should be "trivial".
>
> For comparisons you just need to implement #<=> and then include
> Comparable in your class.
>
This should just be subtracting the two numbers and comparing the
results so that should be easy. And not having to define the other
methods makes it easier.

> And for completeness reasons you should also implement #eql?, #== and
> #hash.
>
Shouldn't #== be inherited from Object? #eql? should be just a matter
of comparing the two numbers after "reducing" the fractions. I am not
sure on the #hash method though. Do you have any ideas where to start?
Thank you for your assistance with this. I am learning a lot from this
exercise.

> Kind regards
>
> robert

Lionel Bouton

4/21/2008 7:02:00 PM

0

Michael W. Ryder wrote:

> [...] That worked except for a minor problem. I first tried a.class ==
> Integer and it failed. I had to use Fixnum to get it to work. I
> thought that Integer being the super class for Fixnum and Bignum I could
> just test for Integer without having to test for both of the subclasses.
> Is there any way to do this?
>

a.is_a? Integer


> I didn't think it would be that easy, but I guess it was.

Principle of Least Surprise :-)
If it looks legit, it probably is and behaves as most people would expect.

Lionel

Robert Klemme

4/21/2008 7:36:00 PM

0

On 21.04.2008 20:48, Michael W. Ryder wrote:
> Robert Klemme wrote:
>> On 20.04.2008 23:43, Michael W. Ryder wrote:
>>> I am part way through implementing a Rational math class to further
>>> my understanding of Ruby and had a couple of questions that I can't
>>> find the answer to in Pickaxe.
>>> The first question regards creating a new instance of the class. The
>>> initialize method expects two integer values. While I have no
>>> problem making sure that they are integers I am not sure what to do
>>> if they are not. Do I return nil or some other result?
>>
>> The return value of #initialize is ignored - always. The proper way
>> to handle this would be to raise an exception - presumably ArgumentError.
>
> My background in older languages is slowing me down. I thought that
> there might be a way to return a value signifying an invalid entry
> without having to worry about exception handling. Haven't got that far
> in Ruby yet.

If I remember this correctly I found exceptions a bit tricky when I
first met them (or did they hit me?) but the concept is much cleaner
than using return values. For example, with exceptions it's much
simpler and cleaner to exit several levels of call stack. Also, since
you can have inheritance hierarchies of exception classes you can nicely
control which errors you catch on which level of your application. And
you do not need complicated if then else or case constructions for it.

>>> how to make it so that Ruby calls the right method when it sees x + y.
>>
>> You need to implement #coerce, #+, #-, #/, #*, #+@, #-@ - that is if
>> you want to have full support of basic math. #coerce is a bit tricky
>> but I am sure there are tutorials around - I just don't have a URL
>> handy. But you can watch how it works:
>>
>
> I have noticed people using things like #+ before. Is that the same as
> just + when defining a method?

Yes. It's just a conventional way to refer to instance methods. ri
uses this as well, you can do "ri String#length". And #-@ and #+@ are
unary operators.

rb(main):001:0> class Foo
rb(main):002:1> def -@;end
rb(main):003:1> end
> nil
rb(main):004:0> -Foo.new
> nil
rb(main):005:0> +Foo.new
oMethodError: undefined method `+@' for #<Foo:0x7ff91648>
from (irb):5
from :0
rb(main):006:0>

> I created a multiplication method using
> 'def *(x)' and it seems to work fine. Hopefully I can use the coerce
> method in the Roman numerals class in Pickaxe to create my coerce method.

I don't have the book right here but I believe that's a good starting point.

>> For comparisons you just need to implement #<=> and then include
>> Comparable in your class.
>>
> This should just be subtracting the two numbers and comparing the
> results so that should be easy. And not having to define the other
> methods makes it easier.

Absolutely.

>> And for completeness reasons you should also implement #eql?, #== and
>> #hash.
>>
> Shouldn't #== be inherited from Object?

It is but Object's #==, #hash and #eql? consider only identical objects
to be equivalent. But you want different objects to be equivalent so
you have to define the equivalence relation - this is what you do by
implementing #== and #eql?. You also need #hash to make instances of
your class behave properly as Hash keys.

> #eql? should be just a matter
> of comparing the two numbers after "reducing" the fractions. I am not
> sure on the #hash method though. Do you have any ideas where to start?

That value should be derived from your reduced value. That way you
ensure that whatever denominator and numerator are used to represent the
same value they fall into the same bucket. And that's what you want
because you want them to be equivalent.

Typically it is derived from object member variable's #hash values via
some combination of shift and XOR. Example:

irb(main):001:0> class Foo
irb(main):002:1> attr_accessor :x, :y
irb(main):003:1> def hash
irb(main):004:2> ((x.hash << 3) ^ y.hash) & 0xFFFF
irb(main):005:2> end
irb(main):006:1> end
=> nil
irb(main):007:0> f=Foo.new
=> #<Foo:0x7ff82e2c>
irb(main):008:0> f.hash
=> 36
irb(main):009:0> nil.hash
=> 4
irb(main):010:0> f.x=10
=> 10
irb(main):011:0> f.hash
=> 172
irb(main):012:0> f.y=20
=> 20
irb(main):013:0> f.hash
=> 129
irb(main):014:0>

> Thank you for your assistance with this. I am learning a lot from this
> exercise.

You're welcome! I hope Ruby makes for a pleasant learning experience.
If it feels rough from time to time, just tell yourself that other
languages are a lot harder. :-)

Kind regards

robert

Robert Dober

4/21/2008 8:19:00 PM

0

On Sun, Apr 20, 2008 at 11:45 PM, Michael W. Ryder
<_mwryder@worldnet.att.net> wrote:
> I am part way through implementing a Rational math class to further my
> understanding of Ruby
which is a good idea and I see that you are in good hands with Robert.
When you are done though have a look into ruby's own rational.rb ( or
even earlier depending on how you prefer) and
compare what they have done with your own code, that might bring some
further enlightenments.

Cheers
Robert

P.S. A second thought, look at it *after* you have finished ;)
R.

Robert Dober

4/21/2008 8:20:00 PM

0

<snip>
> which is a good idea and I see that you are in good hands with Robert.
and of course J=F6el and the others....
R.

Michael W. Ryder

4/21/2008 9:28:00 PM

0

Robert Dober wrote:
> On Sun, Apr 20, 2008 at 11:45 PM, Michael W. Ryder
> <_mwryder@worldnet.att.net> wrote:
>> I am part way through implementing a Rational math class to further my
>> understanding of Ruby
> which is a good idea and I see that you are in good hands with Robert.
> When you are done though have a look into ruby's own rational.rb ( or
> even earlier depending on how you prefer) and
> compare what they have done with your own code, that might bring some
> further enlightenments.
>

I chose this after reading about it in Knuth's 2nd volume. I wanted to
try to implement the binary greatest common divisor algorithm in Ruby.
It took a little effort but I was able to implement it without any Go
Tos. It is hard to go from languages that require jumping around in the
code to one that doesn't. It's a totally different mind set.

> Cheers
> Robert
>
> P.S. A second thought, look at it *after* you have finished ;)
> R.
>

Simon Krahnke

4/22/2008 6:31:00 AM

0

* Robert Klemme <shortcutter@googlemail.com> (2008-04-21) schrieb:

> The return value of #initialize is ignored - always.

Pardon?

,----
| #!/usr/bin/env ruby
|
| class Eins
| def initialize
| 1
| end
|
| def to_s
| initialize.to_s
| end
| end
|
| eins = Eins.new
| puts eins.to_s
`----

Of course prints "1".

You probably mean that Object::new ignores the return value of
#initialize.

mfg, simon .... l