Может ли метод coerce() в Ruby знать, какой именно оператор требует помощи для принуждения?

В Ruby кажется, что большую часть помощи coerce() можно выполнить с помощью

def coerce(something)
  [self, something]
end

то есть, когда

3 + rational

требуется, Fixnum 3 не знает, как обрабатывать добавление Rational, поэтому он обращается за помощью к Rational#coerce, вызывая rational.coerce(3), и этот метод экземпляра coerce сообщит вызывающей стороне:

# 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

Так что, если большинство операторов могут использовать этот метод, но не в ситуации (a - b) != (b - a)? Может ли coerce() знать, какой это оператор, и просто обрабатывать эти особые случаи, просто используя простой [self, something] для обработки всех других случаев, когда (a op b) == (b op a) ? (op — оператор).


person nonopolarity    schedule 11.05.2010    source источник
comment
Зачем задавать три вопроса на coerce, если я дал вам ответ на первый вопрос?   -  person Marc-André Lafortune    schedule 11.05.2010
comment
я получил много понимания из предыдущих постов. на этот раз я хотел бы посмотреть, возможно ли позволить новому классу обрабатывать все новые операции, заменяя операнды, видя, что оператор справляется с этим, и обрабатывать только тот случай, когда обмен не в порядке.   -  person nonopolarity    schedule 11.05.2010


Ответы (2)


Смысл coerce не в том, чтобы знать, какую операцию вы пытаетесь выполнить. Его цель состоит в том, чтобы привести аргумент и self к общей основе. Кроме того, одни и те же операторы могут быть коммутативными в одних классах, а не в других (например, Numeric#+ и Array#+), поэтому ваш небольшой эксплойт coerce, основанный на коммутативности, действительно не окупится.

Вместо того, чтобы заставлять ваш coerce делать то, для чего он не предназначен, вы должны вместо этого создать новый класс (например, ScalarPoint) и использовать его для интерфейса скалярных значений с вашим 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

и

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

и т. д. (NB: код не тестировался)

person Mladen Jablanović    schedule 11.05.2010
comment
Что делать, если нет точки соприкосновения? В моем случае у меня есть класс Money, я хочу Money + Money = Money и Numeric*Money = Money, но не Money*Money = Money и не Numeric + Money = Money. Что я должен делать? - person Alex C; 13.01.2016
comment
Тогда coerce просто не для вас. * - это метод на объекте с левой стороны, и его следует оставить таким (а не просто инвертировать операнды волей-неволей). Подумайте вот о чем: почему в простом Ruby 'a' * 3 работает, а 3 * 'a' нет? Независимо от того, насколько заманчиво было бы неправильно использовать coerce для этого, это просто не его цель. Если вам нужно иметь Numeric * Money = Money, вам нужно сделать это в Numeric, а не в Money. - person Mladen Jablanović; 14.01.2016

Ответ на этот вопрос заключается в том, что вы можете узнать оператор, взглянув на обратную трассировку, но вы не должны этого делать.

Не так устроен механизм принуждения Ruby. Как я ответил в вашем предыдущем вопросе, coerce следует вернуть два эквивалентных значения [a, b], так что a.send(operator, b) будет работать независимо от оператора.

person Marc-André Lafortune    schedule 11.05.2010
comment
на самом деле, мне тоже было интересно, почему принуждение должно возвращать 2 операнда, а затем позволять вызывающей стороне вызывать оператор для первого возвращенного элемента? В равной степени или лучше, чтобы функция coerce принимала операнд и оператор, например + или *, и позволяла функции coerce обрабатывать все вычисления и возвращать только одно значение? - person nonopolarity; 11.05.2010
comment
да, у меня просто сложилось впечатление, что coerce(), возвращающий [self, other] — это очень удобный способ реализовать coerce(), но очевидно, что он не будет работать во всех случаях. Но что, если есть класс, где мы разрешаем 100 - (80,80), и разрешаем 3 * (80,80), но не разрешаем (3,3) * (80,80)? В этом случае функция принуждения должна вернуть два объекта Point и вызвать * для них, чего класс не должен допускать. - person nonopolarity; 11.05.2010
comment
Пожалуйста, прочитайте мой ответ на предыдущий вопрос. Это покажет вам, что вы можете вернуть временную оболочку (скажем, Point::Scalar), которая будет обрабатывать операторы так, как вы хотите. - person Marc-André Lafortune; 11.05.2010