A couple of questions regarding class design

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

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

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

···

On 20.04.2008 23:43, Michael W. Ryder wrote:

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 :wink:
R.

···

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

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.

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

···

On 20.04.2008 23:43, Michael W. Ryder wrote:

Kind regards

    robert

<snip>

which is a good idea and I see that you are in good hands with Robert.

and of course Jöel and the others....
R.

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

···

On Sun, Apr 20, 2008 at 11:45 PM, Michael W. Ryder > <_mwryder@worldnet.att.net> wrote:

Cheers
Robert

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

* 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

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 :slight_smile:
If it looks legit, it probably is and behaves as most people would expect.

Lionel

Robert Klemme 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
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. :slight_smile:

Kind regards

  robert

···

On 21.04.2008 20:48, Michael W. Ryder wrote:

On 20.04.2008 23:43, Michael W. Ryder wrote:

        from :0

And, does it feel better? For me it does - for Knuth apparently not. I haven't felt the need for a goto in ages. I also rarely use continue and break in loops. My impression is that quite a number of cases where either is chosen can be greatly improved and actually simplified by choosing a different loop condition. My 0.02EUR...

If you want to read up on the matter, Wikipedia has quite an exhaustive coverage:

Kind regards

  robert

···

On 21.04.2008 23:28, Michael W. Ryder wrote:

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.

Correct. Hehe, it never occurred to me to invoke #initialize as
ordinary method but I do see the point now. Thanks!

Kind regards

robert

···

2008/4/22, Simon Krahnke <overlord@gmx.li>:

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

--
use.inject do |as, often| as.you_can - without end

You could use a.kind_of? Integer, but why not just call a.to_int? to
get the value to use
instead?

···

On Mon, Apr 21, 2008 at 11:35 AM, Michael W. Ryder <_mwryder@worldnet.att.net> wrote:

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?

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

Correct. Hehe, it never occurred to me to invoke #initialize as
ordinary method but I do see the point now. Thanks!

Hey Robert I would have sworn like you, and in some ways it is almost
better to believe it that way.
However we want to know our language and this might be useful in
metaprogramming, thanx Simon.

Another thing Robert. IIRC correctly Ruby and Knuth do quite agree
about goto, I believe that the usage Knuth made of goto is very much
restricted to emulate exit, return, break, next, raise or throw.
In a language without the aforementioned features their emulation with
goto makes the code much simpler. (I remember how excited I was of
Ada's "exit when ... " and "return" coming from Pascal.

Cheers
Robert

···

On Tue, Apr 22, 2008 at 9:26 AM, Robert Klemme <shortcutter@googlemail.com> wrote:

2008/4/22, Simon Krahnke <overlord@gmx.li>:

Kind regards

robert

--
use.inject do |as, often| as.you_can - without end

--
http://ruby-smalltalk.blogspot.com/

---
Whereof one cannot speak, thereof one must be silent.
Ludwig Wittgenstein

Robert Klemme wrote:

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.

And, does it feel better? For me it does - for Knuth apparently not. I haven't felt the need for a goto in ages. I also rarely use continue and break in loops. My impression is that quite a number of cases where either is chosen can be greatly improved and actually simplified by choosing a different loop condition. My 0.02EUR...

Coming from a background in computers from the 70's when the language was much closer to the metal I never had any problems with Go To or Jump. In assembler there is no way to not use Jumps for loops or conditional processing. Fortran and Basic were much the same way. I believe Knuth's original works were from this era and of course a lot of his code is in the MIX assembler.
I can see the reason to eliminate jumping around in code when possible but think they can sometimes make a program easier to read. I find it easier to read a program that says "If error goto ERROR" over trying to figure out where a Break command goes.

···

On 21.04.2008 23:28, Michael W. Ryder wrote:

If you want to read up on the matter, Wikipedia has quite an exhaustive coverage:
Structured programming - Wikipedia

Kind regards

    robert

Christopher Dicely wrote:

···

On Mon, Apr 21, 2008 at 11:35 AM, Michael W. Ryder > <_mwryder@worldnet.att.net> wrote:

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?

You could use a.kind_of? Integer, but why not just call a.to_int? to
get the value to use
instead?

That will not work if the value is a string or some other object that does not implement to_int. If a = "2" entering a.to_int? gives a NoMethodError. I was trying to find a method that would allow any Integer value but raise an exception for others. I will add a coerce method which may be able to handle input from strings or floats at least some of the time. Thanks for the idea for an improvement to my exercise.

* Robert Klemme <shortcutter@googlemail.com> (08:32) schrieb:

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.

And, does it feel better? For me it does - for Knuth apparently not. I
haven't felt the need for a goto in ages. I also rarely use continue
and break in loops. My impression is that quite a number of cases where
either is chosen can be greatly improved and actually simplified by
choosing a different loop condition. My 0.02EUR...

Tell that Matz & Co.

,----[ ruby-1.9$ grep -c goto *.c | grep -v 0 ]

array.c:2
bignum.c:11
class.c:4
compile.c:18
dir.c:5
dln.c:32
eval.c:3
file.c:2
gc.c:12
io.c:15
marshal.c:5
numeric.c:1
object.c:5
pack.c:6
proc.c:1
process.c:2
random.c:3
range.c:2
re.c:4
regcomp.c:43
regerror.c:1
regexec.c:157
regparse.c:94
ruby.c:16
signal.c:6
sprintf.c:16
string.c:8
thread.c:3
time.c:5
util.c:92
variable.c:3

`----

mfg, simon .... l

···

On 21.04.2008 23:28, Michael W. Ryder wrote:

Robert Klemme wrote:

Coming from a background in computers from the 70's when the language was
much closer to the metal I never had any problems with Go To or Jump. In
assembler there is no way to not use Jumps for loops or conditional
processing. Fortran and Basic were much the same way. I believe Knuth's
original works were from this era and of course a lot of his code is in the
MIX assembler.

Very true, but as I have mentioned one has to be disciplined.

I can see the reason to eliminate jumping around in code when possible but
think they can sometimes make a program easier to read. I find it easier to
read a program that says "If error goto ERROR" over trying to figure out
where a Break command goes.

Then maybe your methods are too complex, what about refactoring :wink:
Honestly I try to avoid methods with more than 10-12 LOC (I do not
succeed all the time) but up to 20LOC might be acceptable.
You really *should* be able to see where the break goes.

This all said, Knuth's MIX code is for sure easier to read than one of
my early Ruby methods :(.

Cheers
Robert

···

On Tue, Apr 22, 2008 at 9:15 PM, Michael W. Ryder <_mwryder@worldnet.att.net> wrote:

> If you want to read up on the matter, Wikipedia has quite an exhaustive
coverage:
> http://en.wikipedia.org/wiki/Structured_programming
>
> Kind regards
>
> robert
>

--
http://ruby-smalltalk.blogspot.com/

---
Whereof one cannot speak, thereof one must be silent.
Ludwig Wittgenstein

Robert Klemme wrote:

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.

And, does it feel better? For me it does - for Knuth apparently not. I haven't felt the need for a goto in ages. I also rarely use continue and break in loops. My impression is that quite a number of cases where either is chosen can be greatly improved and actually simplified by choosing a different loop condition. My 0.02EUR...

Coming from a background in computers from the 70's when the language was much closer to the metal I never had any problems with Go To or Jump.

Well, you had to use what you got, didn't you? I believe the reasons Basic et al had GOTO was historic as you said: programming languages did not have evolved very far from the basis (machine code or assembler if you will).

But later people detected deficiencies of this approach and tried to come up with better solutions. I would not want to miss structured programming and exceptions.

In assembler there is no way to not use Jumps for loops or conditional processing. Fortran and Basic were much the same way. I believe Knuth's original works were from this era and of course a lot of his code is in the MIX assembler.
I can see the reason to eliminate jumping around in code when possible but think they can sometimes make a program easier to read. I find it easier to read a program that says "If error goto ERROR" over trying to figure out where a Break command goes.

Well, for error handling there is a different mechanism in modern languages: exceptions. :slight_smile:

Kind regards

  robert

···

On 22.04.2008 21:12, Michael W. Ryder wrote:

On 21.04.2008 23:28, Michael W. Ryder wrote: