views:

57

answers:

2

In Ruby, it seems that a lot of coerce() help can be done by

def coerce(something)
  [self, something]
end

that's is, when

3 + rational

is needed, Fixnum 3 doesn't know how to handle adding a Rational, so it asks Rational#coerce for help by calling rational.coerce(3), and this coerce instance method will tell the caller:

# I know how to handle rational + something, so I will return you the following:
[self, something]
# so that now you can invoke + on me, and I will deal with Fixnum to get an answer

So what if most operators can use this method, but not when it is (a - b) != (b - a) situation? Can coerce() know which operator it is, and just handle those special cases, while just using the simple [self, something] to handle all the other cases where (a op b) == (b op a) ? (op is the operator).

+1  A: 

The answer to this question is that you can know the operator by looking at the backtrace but you shouldn't do that.

That is not how the coerce mechanism of Ruby has been designed. As I answered in your previous question, coerce should return two equivalent values [a, b] such that a.send(operator, b) will work, whatever the operator.

Marc-André Lafortune
actually, i was wondering too, why coerce has to return 2 operands and then let the caller invoke the operator on the first returned element? Is it equally feasible or better that coerce takes an operand and an operator, such as "+" or "*", and let the coerce function deal with all calculations and return just 1 value?
動靜能量
yes, i was just under the impression that coerce() returning [self, other] is a very handy way to implement coerce(), but obviously it won't work for all cases. But what if there is a class, where we allow 100 - (80,80), and allow 3 * (80,80), but won't allow (3,3) * (80,80)? In that case, the coerce function needs to return two Point objects, and invoke "*" on it, which the class is not to allow.
動靜能量
Please read my answer to your previous question. It'll show you that you can return a temporary wrapper (say Point::Scalar) which will handle the operators the way you want.
Marc-André Lafortune
+1  A: 

The point of coerce is not to know what operation you are trying to perform. Its purpose is to bring the argument and self to a common ground. Additionally, same operators can be commutative in certain classes, and not in other (Numeric#+ and Array#+, for example), so your small commutativity-based coerce exploit really won't pay off.

Instead of pushing your coerce to do what it's not intended to, you should create a new class instead (such as ScalarPoint, for example), and use it to interface scalar values with your Point:

class ScalarPoint
  attr_reader :val

  def initialize(val)
    @val = val
  end

  def +(arg)
    case arg
    when Point:
      Point.new(@val + arg.x, @val + arg.y)
    when ScalarPoint:
      ScalarPoint.new(arg.val + @val)
    else
      raise "Unknown operand!"
    end
  end

  # define other operators, such as * etc

  def coerce(arg)
    return [ScalarPoint.new(arg), self] if arg.is_a?(Numeric)
    raise "Can't handle"
  end
end

and

class Point
  def coerce(arg)
    [ScalarPoint.new(arg), self] if arg.is_a?(Numeric)
    raise "Can't handle"
  end
end

etc. (NB: code not tested)

Mladen Jablanović