From: "marcandre (Marc-Andre Lafortune)" Date: 2013-04-08T12:21:49+09:00 Subject: [ruby-core:54092] [ruby-trunk - Feature #8223] Make Matrix more omnivorous. Issue #8223 has been updated by marcandre (Marc-Andre Lafortune). Category set to lib Assignee set to marcandre (Marc-Andre Lafortune) Priority changed from Normal to Low Summary: I could consider injecting nothing instead of 0, but can not consider a generic `SomeClass.zero`. I don't feel your example is a good justification of a need of not injecting 0. boris_stitnicky (Boris Stitnicky) wrote: > Let's imagine a class Metre, whose instances represent physical magnitudes in metres. > > class Metre > attr_reader :magnitude > def initialize magnitude; @magnitude = magnitude end > def to_s; magnitude.to_s + ".m" end > end > > Let's say that metres can be multiplied by a number: > And that they can be summed up with other magnitudes in metres, but, as a feature, > not with numbers (apples, pears, seconds, kelvins...). Shouldn't 0 be specially allowed, i.e 1.m + 0 == 1.m? > And with defined #coerce: > > class Metre > def coerce other; [ self, other ] end > end This is not the way to define `coerce`. I'm sorry there are not precise specs for coercion, but the idea is to return [transform(other), transform2(self)] such that both elements are compatible. You are reversing the order of things! AFAIK, there is no guarantee this will continue to work as you want it to. Check how the Matrix class does it (by using an intermediary Scalar class). But I'm not convinced you have the right approach with the Metre class. Have you tried defining instead a class `MeasureWithPhysicalUnit` (feel free to shorten the name :-)). You'll need a class "PhysicalUnit" too. In that case, it would be easy to coerce a numeric value: class Metre def coerce other; [ MeasureWithPhysicalUnit.new(other, ''), self ] end end This probably will be a bit more complex. This should allow a more sensible way of working, and 2.m * 3.m would == 6.m^2 > It works, but the joy does not last long. The user will fail miserably if ze wants to perform matrix multiplication: Right. Just allow addition with 0, which you have zero reason not to implement. > But this is not a very systematic solution. Actually, I think that the Matrix class would be more flexible, if, instead of simply using 0, it asked the elements of the matrix what their zero Definitely not necessary. As you realized in a later email, the 0 is injected in case of empty matrices. If this really was a problem for you, it would be easy to inject the first term in the summation instead (and handle the empty matrix case another way). In no case could a Metre.zero be used, because there is Meter to get in case of zero matrices... ---------------------------------------- Feature #8223: Make Matrix more omnivorous. https://bugs.ruby-lang.org/issues/8223#change-38343 Author: boris_stitnicky (Boris Stitnicky) Status: Open Priority: Low Assignee: marcandre (Marc-Andre Lafortune) Category: lib Target version: Let's imagine a class Metre, whose instances represent physical magnitudes in metres. class Metre attr_reader :magnitude def initialize magnitude; @magnitude = magnitude end def to_s; magnitude.to_s + ".m" end end Let's say that metres can be multiplied by a number: class Metre def * multiplicand case multiplicand when Numeric then Metre.new( magnitude * multiplicand ) else raise "Metres can only be multiplied by numbers, multiplication by #{multiplicand.class} attempted!" end end end And that they can be summed up with other magnitudes in metres, but, as a feature, not with numbers (apples, pears, seconds, kelvins...). class Metre def + summand case summand when Metre then Metre.new( magnitude + summand.magnitude ) else raise "Metres can only be summed with metres, summation with #{summand.class} attempted!" end end end Now with one more convenience constructor Numeric#m: class Numeric def m; Metre.new self end end We can write expressions such as 3.m + 5.m #=> 8.m 3.m * 2 #=> 6.m And with defined #coerce: class Metre def coerce other; [ self, other ] end end Also this expression is valid: 2 * 3.m #=> 6.m Before long, the user will want to make a matrix of magnitudes: require 'matrix' mx = Matrix.build 2, 2 do 1.m end #=> Matrix[[1.m, 1.m], [1.m, 1.m]] It works, but the joy does not last long. The user will fail miserably if ze wants to perform matrix multiplication: cv = Matrix.column_vector [1, 1] mx * cv #=> RuntimeError: Metres can only be summed with metres, summation with Fixnum attempted! # where 2.m would be expected In theory, everything should be O.K., since Metre class has both metre summation and multiplication by a number defined. The failure happens due to the internal workings of the Matrix class, which assumes that the elements can be summed together with numeric 0. But it is a feature of metres, that they are picky and allow themselves to be summed only with other Metre instances. In my real physical units library that I have written, I have solved this problem by defining an ��ber zero object that produces the expected result, when summed with objects, that would otherwise not lend themselves to summation with ordinary numeric 0, and patching the Matrix class so that it uses this ��ber zero instead of the ordinary one. But this is not a very systematic solution. Actually, I think that the Matrix class would be more flexible, if, instead of simply using 0, it asked the elements of the matrix what their zero is, as in: class << Metre def zero; new 0 end end But of course, that would also require that ordinary numeric classes can tell what their zero is, as in: def Integer.zero; 0 end def Float.zero; 0.0 end def Complex.zero; Complex 0.0, 0.0 end # etc. I think that this way of doing things (that is, having #zero methods in numeric classes and making Matrix actually require the class of the objects in it to have public class method #zero defined) would make everything more consistent and more algebra-like. I am having this problem for already almost half a year, but I only gathered courage today to encumber you guys with this proposal. Please don't judge me harshly for it. I have actually already seen something like this, in particular with bigdecimal's Jacobian (http://ruby-doc.org/stdlib-2.0/libdoc/bigdecimal/rdoc/Jacobian.html), which requires that the object from which the Jacobian is computed implements methods #zero, #one, #two etc. Sorry again. -- http://bugs.ruby-lang.org/