From: Robert Klemme Date: 2010-05-10T22:13:49+09:00 Subject: Re: In Ruby, how does coerce() actually work? 2010/5/10 Jian Lin : > It is said that when we have a class Point and knows how to perform > point * 3 like the following: > >    class Point > >      def initialize(x,y) >        @x, @y = x, y >      end > >      def *(c) >        Point.new(@x * c, @y * c) >      end > >    end > >    point = Point.new(1,2) >    p point >    p point * 3 > > Output: > >    # >    # > > but then, > >    3 * point > > is not understood: > >    Point can't be coerced into Fixnum (TypeError) > > So we need to further define an instance method `coerce`: > > >    class Point >      def coerce(something) >        [self, something] >      end >    end There is no type checking and conversion. Normally you would also return the argument first but in this case exchanging the order seems OK since the multiplication is not really symmetric. The std lib does it like this: irb(main):001:0> 1.2.coerce 3 => [3.0, 1.2] irb(main):002:0> Please see my blog post for a complete example: http://blog.rubybestpractices.com/posts/rklemme/019-Complete_Numeric_Class.html > The question is: > > 1) who invokes point.coerce(3) ?  Is it Ruby automatically, or is it > some code inside of `*` method of Fixnum by catching an exception?  Or > is it by case statement that when it doesn't know one of the known > types, then call coerce? The latter. > 2) Does coerce always need to return an array of 2 elements?  Can it be > no array?  Or can it be an array of 3 elements? It always needs to return an array of two elements - that's the contract. > 3) And is the rule that, the original operator (or method) `*` will then > be invoked on element 0, with the argument of element 1?  (element 0 and > element 1 are the two elements in that array returned by coerce) Exactly. >  Who does it? Is it done by Ruby or is it done by code in Fixnum? The latter. >  If it is > done by code in Fixnum, then it is a "convention" that everybody follows > when doing a coerce? I'd rather say it's the contract of #coerce but you can call it a "convention" as well. > 4) So it is really hard to add something to Fixnum's instance method > `coerce`?  It already has a lot of code in it and we can't just add a > few lines to enhance it (but will we ever want to?) You do not need to add to Fixnum's coerce. Rather you add to your operator implementation. > 5) The coerce() in the Point class is quite generic and it works with > `*` or `+` because they are transitive.  What if it is not transitive, > such as if we define Point minus Fixnum to be: > >    point = Point.new(100,100) >    point - 20       # ==> to give  (80,80) >    80 - point       # ==> to give  (-80,-80) I think you confused "transitive" and "commutative". http://en.wikipedia.org/wiki/Commutative http://en.wikipedia.org/wiki/Transitive_relation Commutativity is the exact reason for having #coerce. Please see the blog post. Kind regards robert -- remember.guy do |as, often| as.you_can - without end http://blog.rubybestpractices.com/