From: oinkoink+unet@... (Bret Jolly) Date: 2003-05-04T07:12:42+09:00 Subject: Re: MathN Jim Freeze 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? > > 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: (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 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/