I’m trying to get to grips with the ‘mathn’ library. I can see what it
does, but I’m trying to see why it does it. Is it emulating a mathn
library in some other language, or is it simply there to make arithmetic
more accurate?
Cheers
Dave
I’m trying to get to grips with the ‘mathn’ library. I can see what it
does, but I’m trying to see why it does it. Is it emulating a mathn
library in some other language, or is it simply there to make arithmetic
more accurate?
Cheers
Dave
Dave Thomas dave@pragprog.com wrote in message news:3EA7FF8F.3080500@pragprog.com…
I’m trying to get to grips with the ‘mathn’ library. I can see what it
does, but I’m trying to see why it does it. Is it emulating a mathn
library in some other language, or is it simply there to make arithmetic
more accurate?
In math, 7 is an integer. It is also a rational
number, a real number, and a complex number. The
mathn library allows 7 to effectively be all of these
by making Float, Fixnum, Bignum, Rational, and Complex
all play nice with one another. In a program that
requires mathn, we can treat 7 according to its
mathematical properties rather than the properties
of the class hierarchy. Also, as I’ve mentioned
before, matrix.rb needs mathn.rb in order to work
properly.
The OO paradigm is rather alien to the mathematical
mind. Mathematicians might think of the expression
“3 + 7” in various ways, depending on their background
and what they had for breakfast. But I’m sure damn
few of them think of it as the object 3 receiving the
method + with an argument of 7 ! So I’ve been surprised
at how easy it is to write mathematical programs in
ruby. In fact, ruby is very good at math, even quite
sophisticated math. Doubters should take a look at
the algebra package in the RAA.
Ruby’s reflective powers and dynamic nature allow
one to get around OO head-scratchers. For example,
I’ve been working on a polynomial package (pnom.rb,
coming soon to an RAA near you). I wanted to allow
anything that represented some sort of complex number
as a coefficient, so I required mathn. I needed to add
a bunch of methods to the classes representing complex
numbers according to mathn, so that they would play
nice with my new Pnom class. But what if the user
created a Numeric class fitting the bill, for example,
a class QuadRat of quadratic rationals (numbers of the
form a + b*sqrt(c), where a, b, and c are rational),
I wanted the user to be able to use that class too.
With ruby I was able to rewrite a bunch of methods
in all the appropriate classes without knowing about
classes the user wanted to add in advance. I also wanted
the package to be well behaved when it was reloaded.
Here is how I handled them:
PlayNiceWithPnom = {‘+’=>‘Plus’, ‘*’=>‘Mul’, ‘<=>’=>‘Spaceship’,
‘==’=>‘Equal’, ‘<’=>‘Less’, ‘>’=>‘Greater’, ‘<=’=>‘LessEqual’,
‘>=’=>‘GreaterEqual’}
PlayNiceWithPnom.each {|key, value|
eval(%Q% PlayNiceWithPnom#{value} = <<-EOS
alias prePnom_#{value} #{key}
def #{key}(other)
if defined?(Pnom) and other.kind_of?(Pnom)
Pnom.new(self) #{key} other
else
prePnom_#{value}(other)
end
end
EOS
%)
}
ObjectSpace.each_object(Class) {|klass|
if klass.ancestors.include?(Numeric)
PlayNiceWithPnom.each{|key, value|
if klass.public_instance_methods.include?(key)
unless klass.public_instance_methods.include?(“prePnom_#{value}”)
eval(%Q% klass.module_eval(PlayNiceWithPnom#{value})%)
end
end
}
end
}
I shudder to think of trying to accomplish this in Python!
Regards, Bret
Dave Thomas dave@pragprog.com wrote in message news:3EA7FF8F.3080500@pragprog.com…
I’m trying to get to grips with the ‘mathn’ library. I can see what it
does, but I’m trying to see why it does it. Is it emulating a mathn
library in some other language, or is it simply there to make arithmetic
more accurate?
Let me try again, since the usenet gremlins seem to have intercepted
my earlier post (although Jim Freeze saw it somehow).
The easiest way to see the importance of
the mathn library is to consider what happens
if you try to use Rational, Complex, or Matrix,
without requiring mathn. (The mathn library
requires rational.rb, complex.rb, and matrix.rb.
and then makes them “play nice” with one another.)
First look at complex numbers without mathn.
irb(main):001:0> require ‘complex’
true
irb(main):002:0> minus_two = Complex(-2, 0)
Complex(-2, 0)
irb(main):003:0> minus_two < 1
false # fubar!
irb(main):004:0> minus_two > 1
true # fubar!
irb(main):001:0> require ‘mathn’
true
irb(main):002:0> minus_two = Complex(-2, 0)
-2
irb(main):003:0> minus_two < 1
true # correct
irb(main):004:0> minus_two > 1
false # correct
Now try matrix.rb
irb(main):001:0> require ‘matrix’
true
irb(main):002:0> m = Matrix[[2, 7], [3, 4]]
Matrix[[2, 7], [3, 4]]
irb(main):003:0> m.det
-6 # fubar!
irb(main):004:0> m.inv
Matrix[[-1, 1], [0, -1]] # fubar!
irb(main):001:0> require ‘mathn’
true
irb(main):002:0> m = Matrix[[2, 7], [3, 4]]
Matrix[[2, 7], [3, 4]]
irb(main):003:0> m.det
-13 # correct
irb(main):004:0> m.inv
Matrix[[-4/13, 7/13], [3/13, -2/13]] # correct
Now try rational.rb
irb(main):001:0> require ‘rational’
true
irb(main):002:0> two = Rational(2, 1)
Rational(2, 1)
irb(main):003:0> 2 == two
true # Gratifying, but…
irb(main):004:0> two/3
Rational(2, 3)
irb(main):005:0> 2/3
0 # inappropriate when Rational is available.
irb(main):001:0> require ‘mathn’
true
irb(main):002:0> two = Rational(2, 1)
2
irb(main):003:0> two/3
2/3
irb(main):004:0> 2/3
2/3 # integers and rationals now play nice
In conclusion, if you want to use the built-in
libraries rational, complex, and matrix, you
should require mathn.
Regards, Bret
The above seems very interesting, but it is not clear to me
exactly how it works.
Would you mind providing a walk-thru description of what it does?
Thanks
On Monday, 28 April 2003 at 7:16:39 +0900, Bret Jolly wrote:
make infix operators in Numeric classes play nice with Pnom
PlayNiceWithPnom = {‘+’=>‘Plus’, ‘*’=>‘Mul’, ‘<=>’=>‘Spaceship’,
‘==’=>‘Equal’, ‘<’=>‘Less’, ‘>’=>‘Greater’, ‘<=’=>‘LessEqual’,
‘>=’=>‘GreaterEqual’}
PlayNiceWithPnom.each {|key, value|
eval(%Q% PlayNiceWithPnom#{value} = <<-EOS
alias prePnom_#{value} #{key}
def #{key}(other)
if defined?(Pnom) and other.kind_of?(Pnom)
Pnom.new(self) #{key} other
else
prePnom_#{value}(other)
end
end
EOS
%)
}
ObjectSpace.each_object(Class) {|klass|
if klass.ancestors.include?(Numeric)
PlayNiceWithPnom.each{|key, value|
if klass.public_instance_methods.include?(key)
unless klass.public_instance_methods.include?(“prePnom_#{value}”)
eval(%Q% klass.module_eval(PlayNiceWithPnom#{value})%)
end
end
}
end
}I shudder to think of trying to accomplish this in Python!
Don’t change the reason, just change the excuses!
– Joe Cointment
Bret Jolly wrote:
Dave Thomas dave@pragprog.com wrote in message news:3EA7FF8F.3080500@pragprog.com…
I’m trying to get to grips with the ‘mathn’ library. I can see what it
does, but I’m trying to see why it does it. Is it emulating a mathn
library in some other language, or is it simply there to make arithmetic
more accurate?Let me try again, since the usenet gremlins seem to have intercepted
my earlier post (although Jim Freeze saw it somehow).
That’s an excellent set of examples. If you don’t mind, I might borrow
some of them for some documentation I’m doing… ![]()
Cheers
Dave
Jim Freeze jim@freeze.org wrote in message news:20030427200010.A56234@freeze.org…
make infix operators in Numeric classes play nice with Pnom
PlayNiceWithPnom = {‘+’=>‘Plus’, ‘*’=>‘Mul’, ‘<=>’=>‘Spaceship’,
‘==’=>‘Equal’, ‘<’=>‘Less’, ‘>’=>‘Greater’, ‘<=’=>‘LessEqual’,
‘>=’=>‘GreaterEqual’}
PlayNiceWithPnom.each {|key, value|
eval(%Q% PlayNiceWithPnom#{value} = <<-EOS
alias prePnom_#{value} #{key}
def #{key}(other)
if defined?(Pnom) and other.kind_of?(Pnom)
Pnom.new(self) #{key} other
else
prePnom_#{value}(other)
end
end
EOS
%)
}
ObjectSpace.each_object(Class) {|klass|
if klass.ancestors.include?(Numeric)
PlayNiceWithPnom.each{|key, value|
if klass.public_instance_methods.include?(key)
unless klass.public_instance_methods.include?(“prePnom_#{value}”)
eval(%Q% klass.module_eval(PlayNiceWithPnom#{value})%)
end
end
}
end
}I shudder to think of trying to accomplish this in Python!
The above seems very interesting, but it is not clear to me
exactly how it works.Would you mind providing a walk-thru description of what it does?
Thanks
Sure. I am making a class Pnom of polynomials in
one variable. It’s principally an API, but it can also
be used as a calculator. It’s designed to play nice
with the standard library provided by mathn, and also
to be usable with Numeric classes that the user
provides.
The coefficients of a Pnom are (mathematically)
complex numbers. They might have the Ruby class
of Fixnum, Bignum, Rational, Complex, or a subclass
of Numeric provided by the user. A numeric class
provided by the user should be designed to play nice
with the standard classes of mathn, much in the way
in which mathn makes these classes play nice with
one another.
Though the coefficients must be some sort of
complex number, arguments for evaluation can be
more general. For example, we could evaluate a
Pnom on a Matrix (i.e., substitute the Matrix for
the Pnom’s variable and calculate the result).
However, the tricks for this are not part of the
code you asked about.
In math, in a context dealing with polynomials,
we can treat a constant as a constant polynomial.
Thus I can add a constant to a polynomial to get
a new polynomial: (X2 - 1) + 3 == X2 + 2.
To implement this in ruby, we can consider the
possibility that the input is Numeric in the +
method of class Pnom:
class Pnom
def +(q)
if q.kind_of?(Numeric)
# do something
else
# do something else
end
end
end
However, this is not enough. We want addition
to be commutative. Thus we have to tell each
subclass of Numeric what to do when adding a Pnom.
For example:
class Rational
alias PrePnom_Plus + # alias before redefining
def +(other)
if defined?(Pnom) and other.kind_of?(Pnom)
Pnom.new(self) + other
else
prePnom_Plus(other)
end
end
Here, Pnom.new is not the normal constructor
(which would just return a number in response
to a single number input number), but a special
constructor which is only used internally, and
which will make a Pnom of degree 0 (or of degree
minus infinity if the input is 0).
I want to do something like this for a lot
of other methods: *, <=>, <, etc. I want to
do this for all the Numeric classes of the
standard library, and also for other subclasses
of Numeric that the user may have written or
downloaded from the RAA. Such classes have
to be required before pnom.rb is required.
As an additional complication, I want
to avoid redefining methods which have already
been redefined, as might happen if pnom.rb
is reloaded.
First I make a hash whose keys are the (string
representations of) methods I want to redefine and
whose values are suffixes to be used in the
alias-before-redefinition. For example, ‘+’ => ‘Plus’
since I will alias it to PrePnom_Plus.
PlayNiceWithPnom = {‘+’=>‘Plus’, ‘*’=>‘Mul’, ‘<=>’=>‘Spaceship’,
‘==’=>‘Equal’, ‘<’=>‘Less’, ‘>’=>‘Greater’, ‘<=’=>‘LessEqual’,
‘>=’=>‘GreaterEqual’}
Then with for each method in PlayNiceWithPnom,
I create a string representing the alias-before-
redefinition and the new method definition. The
strings will be named PlayNiceWithPnom_Plus,
PlayNiceWithPnom_Mul, PlayNiceWithPnom_Spaceship,
and so forth.
PlayNiceWithPnom.each {|key, value|
eval(%Q% PlayNiceWithPnom#{value} = <<-EOS
alias prePnom_#{value} #{key}
def #{key}(other)
if defined?(Pnom) and other.kind_of?(Pnom)
Pnom.new(self) #{key} other
else
prePnom_#{value}(other)
end
end
EOS
%)
}
Now I search the object space for every
class klass which is a subclass of Numeric. For
each such class, and for each of the methods
I’m interested in, I check
(1) if the method is indeed a public_instance_method
of the class. If the class doesn’t use this
method I skip it.
(2) if the class has already redefined the method
to play nice with Pnom. For example, if + has
already been redefined, there will be an instance method
PrePnomPlus defined in the class.
If these conditions hold, I use klass.module_eval
to insert the definition in the class klass.
I hope this helps! Regards, Bret
Bret Jolly
http://www.rexx.com/~oinkoink/
On Monday, 28 April 2003 at 7:16:39 +0900, Bret Jolly wrote:
Dave Thomas dave@pragprog.com wrote in message news:3EB415CC.7000302@pragprog.com…
That’s an excellent set of examples. If you don’t mind, I might borrow
some of them for some documentation I’m doing…
I’d be an honored piggy. After all, you and Andy are the ones
who convinced me to learn Ruby. And your books have taught
me a lot about being a programmer.
I learned about the matrix problems on a small English
corner of a Japanese Ruby site: apparently these problems
are well known in some circles. I may even have copied my
matrix example from them verbatim…I can’t recall for
sure. Anyway, the matrix.rb problems are the most serious.
You can’t correctly calculate the determinant of a matrix with
integer entries, even though it is a well-defined integer
result. matrix.rb is downright broken without mathn.
Regards, Bret
oinkoink+unet@rexx.com (Bret Jolly) wrote in message news:7e7131a1.0305031356.174fec7e@posting.google.com…
If these conditions hold, I use klass.module_eval
to insert the definition in the class klass.
That was pretty slick, wasn’t it? Such slickness
can produce inscrutable bugs. The last bit of code,
which I meant to repeat but lost somehow, was this:
ObjectSpace.each_object(Class) {|klass|
if klass.ancestors.include?(Numeric)
PlayNiceWithPnom.each{|key, value|
if klass.public_instance_methods.include?(key)
unless klass.public_instance_methods.include?(“prePnom_#{value}”)
eval(%Q% klass.module_eval(PlayNiceWithPnom#{value})%)
end
end
}
end
}
I was just patting myself on the back for being
so clever when I ran into a bug. This wasn’t working
properly for Bignum ‘>’. I investigated Bignum:
irb(main):001:0> Bignum.public_instance_methods
=> [“**”, “quo”, “<=>”, “-”, “coerce”, “==”, “”, “modulo”, “|”,
“/”, “size”, “eql?”, “<<”, “%”, “>>”, “~”, “&”, “divmod”, “to_s”,
“hash”, “^”, “abs”, “to_f”, “div”, “*”, “remainder”, “+”, “-@”]
irb(main):002:0> Bignum.ancestors
=> [Bignum, Integer, Precision, Numeric, Comparable, Object, Kernel]
Bignum gets its comparison methods >, <, >=, <= from
Comparable via the spaceship <=>, so they weren’t listed
in Bignum.public_instance_methods, and my clever hack failed.
I kluged my way around it, and now I’m trying to think of
something better than a kluge.
Regards, Bret
Hi Bret:
Thanks for the explanation. This actually looks like
a process that could be 'patterned' and used for
more than just extending numerics.
Have you thought about abstracting this into a Module
that had general use?
On Sunday, 4 May 2003 at 7:12:42 +0900, Bret Jolly wrote:
Jim Freeze <jim@freeze.org> wrote in message news:<20030427200010.A56234@freeze.org>...
> On Monday, 28 April 2003 at 7:16:39 +0900, Bret Jolly wrote:
> >
> > # make infix operators in Numeric classes play nice with Pnom
> > PlayNiceWithPnom = {'+'=>'Plus', '*'=>'Mul', '<=>'=>'Spaceship',
> > '=='=>'Equal', '<'=>'Less', '>'=>'Greater', '<='=>'LessEqual',
> > '>='=>'GreaterEqual'}
> > PlayNiceWithPnom.each {|key, value|
> > eval(%Q% PlayNiceWithPnom#{value} = <<-EOS
> > alias prePnom_#{value} #{key}
> > def #{key}(other)
> > if defined?(Pnom) and other.kind_of?(Pnom)
> > Pnom.new(self) #{key} other
> > else
> > prePnom_#{value}(other)
> > end
> > end
> > EOS
> > %)
> > }
> > ObjectSpace.each_object(Class) {|klass|
> > if klass.ancestors.include?(Numeric)
> > PlayNiceWithPnom.each{|key, value|
> > if klass.public_instance_methods.include?(key)
> > unless klass.public_instance_methods.include?("prePnom_#{value}")
> > eval(%Q% klass.module_eval(PlayNiceWithPnom#{value})%)
> > end
> > end
> > }
> > end
> > }
> >
> > I shudder to think of trying to accomplish this in Python!
>
> The above seems very interesting, but it is not clear to me
> exactly how it works.
>
> Would you mind providing a walk-thru description of what it does?
>
> ThanksSure. I am making a class Pnom of polynomials in
one variable. It's principally an API, but it can also
be used as a calculator. It's designed to play nice
with the standard library provided by mathn, and also
to be usable with Numeric classes that the user
provides.The coefficients of a Pnom are (mathematically)
complex numbers. They might have the Ruby class
of Fixnum, Bignum, Rational, Complex, or a subclass
of Numeric provided by the user. A numeric class
provided by the user should be designed to play nice
with the standard classes of mathn, much in the way
in which mathn makes these classes play nice with
one another.Though the coefficients must be some sort of
complex number, arguments for evaluation can be
more general. For example, we could evaluate a
Pnom on a Matrix (i.e., substitute the Matrix for
the Pnom's variable and calculate the result).
However, the tricks for this are not part of the
code you asked about.In math, in a context dealing with polynomials,
we can treat a constant as a constant polynomial.
Thus I can add a constant to a polynomial to get
a new polynomial: (X**2 - 1) + 3 == X**2 + 2.
To implement this in ruby, we can consider the
possibility that the input is Numeric in the +
method of class Pnom:class Pnom
def +(q)
if q.kind_of?(Numeric)
# do something
else
# do something else
end
end
endHowever, this is not enough. We want addition
to be commutative. Thus we have to tell each
subclass of Numeric what to do when adding a Pnom.
For example:class Rational
alias PrePnom_Plus + # alias before redefiningdef +(other)
if defined?(Pnom) and other.kind_of?(Pnom)
Pnom.new(self) + other
else
prePnom_Plus(other)
end
endHere, Pnom.new is not the normal constructor
(which would just return a number in response
to a single number input number), but a special
constructor which is only used internally, and
which will make a Pnom of degree 0 (or of degree
minus infinity if the input is 0).I want to do something like this for a lot
of other methods: *, <=>, <, etc. I want to
do this for all the Numeric classes of the
standard library, and also for other subclasses
of Numeric that the user may have written or
downloaded from the RAA. Such classes have
to be required before pnom.rb is required.As an additional complication, I want
to avoid redefining methods which have already
been redefined, as might happen if pnom.rb
is reloaded.First I make a hash whose keys are the (string
representations of) methods I want to redefine and
whose values are suffixes to be used in the
alias-before-redefinition. For example, '+' => 'Plus'
since I will alias it to PrePnom_Plus.PlayNiceWithPnom = {'+'=>'Plus', '*'=>'Mul', '<=>'=>'Spaceship',
'=='=>'Equal', '<'=>'Less', '>'=>'Greater', '<='=>'LessEqual',
'>='=>'GreaterEqual'}Then with for each method in PlayNiceWithPnom,
I create a string representing the alias-before-
redefinition and the new method definition. The
strings will be named PlayNiceWithPnom_Plus,
PlayNiceWithPnom_Mul, PlayNiceWithPnom_Spaceship,
and so forth.PlayNiceWithPnom.each {|key, value|
eval(%Q% PlayNiceWithPnom#{value} = <<-EOS
alias prePnom_#{value} #{key}
def #{key}(other)
if defined?(Pnom) and other.kind_of?(Pnom)
Pnom.new(self) #{key} other
else
prePnom_#{value}(other)
end
end
EOS
%)
}Now I search the object space for every
class klass which is a subclass of Numeric. For
each such class, and for each of the methods
I'm interested in, I check
(1) if the method is indeed a public_instance_method
of the class. If the class doesn't use this
method I skip it.
(2) if the class has already redefined the method
to play nice with Pnom. For example, if + has
already been redefined, there will be an instance method
PrePnomPlus defined in the class.If these conditions hold, I use klass.module_eval
to insert the definition in the class klass.I hope this helps! Regards, Bret
Bret Jolly
http://www.rexx.com/~oinkoink/
--
Jim Freeze
----------
May a Misguided Platypus lay its Eggs in your Jockey Shorts
I was just patting myself on the back for being
so clever when I ran into a bug. This wasn’t working
properly for Bignum ‘>’. I investigated Bignum:
How’d you find the bug (or rather, that there WAS a bug)? Unit tests?
Jim Freeze jim@freeze.org wrote in message news:20030508085056.A8779@freeze.org…
Hi Bret:
Thanks for the explanation. This actually looks like
a process that could be ‘patterned’ and used for
more than just extending numerics.Have you thought about abstracting this into a Module
that had general use?
Hmmm…interesting idea. I’m not sure what the
proper generalization would be. It is one thing to
apply this pattern to a specific problem, and another
to make a module that would apply this pattern
generally. Methinks that applying this pattern blindly
would be pretty dangerous. Also, I haven’t come up
with any good ideas for what the API would look like.
In fact, over the last few days I have been growing
increasingly skeptical that this pattern is even
appropriate for my application. I should be able to
accomplish the same thing less obtrusively using
the coerce mechanism, but so far I haven’t been able
to make it work, probably because I don’t understand
“coerce” very well.
Nonetheless, you’ve given me something interesting
to think about. I shall go and think some more.
Regards, Bret
“Mike Campbell” michael_s_campbell@yahoo.com wrote in message news:NFBBKBEMGLGCIPPFGHOLCEMPDIAA.michael_s_campbell@yahoo.com…
How’d you find the bug (or rather, that there WAS a bug)? Unit tests?
Yep, unit tests. The new Test::Unit in the Ruby 1.8 preview
is much easier to use than its predecessors. There’s no longer
any reason to put off writing the tests :-).