Сказано это, когда у нас есть класс Point
и знает, как выполнить point * 3
как следующее:
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
Вывод:
#<Point:0x336094 @x=1, @y=2>
#<Point:0x335fa4 @x=3, @y=6>
но затем,
3 * point
не понят:
Point
не может быть принужден вFixnum
(TypeError
)
Таким образом, мы должны далее определить метод экземпляра coerce
:
class Point
def coerce(something)
[self, something]
end
end
p 3 * point
Вывод:
#<Point:0x3c45a88 @x=3, @y=6>
Таким образом, это сказано это 3 * point
совпадает с 3.*(point)
. Таким образом, метод экземпляра *
берет аргумент point
и вызовите на объект 3
.
Теперь, начиная с этого метода *
не знает, как умножить точку, таким образом,
point.coerce(3)
будет назван и возвратит массив:
[point, 3]
и затем *
еще раз применяется к нему, который верен?
Теперь, это понято, и у нас теперь есть новое Point
объект, как выполнено методом экземпляра *
из Point
класс.
Вопрос:
Кто вызывает point.coerce(3)
? Это - Ruby автоматически или является этим некоторый код в *
метод Fixnum
путем ловли исключения? Или это case
оператор, что, когда это не будет знать один из известных типов, затем звоните coerce
?
Делает coerce
всегда должен возвращать массив 2 элементов? Это не может быть никакой массив? Или это может быть массив 3 элементов?
И правило что, исходный оператор (или метод) *
будет затем вызван на элемент 0, с аргументом элемента 1? (Элемент 0 и элемент 1 являются этими двумя элементами в том массиве, возвращенном coerce
.), Кто делает это? Это сделано Ruby, или это сделано кодом в Fixnum
? Если это сделано кодом в Fixnum
, затем это - "конвенция", за которой все следуют при выполнении приведения?
Так мог это быть кодом в *
из Fixnum
выполнение чего-то вроде этого:
class Fixnum
def *(something)
if (something.is_a? ...)
else if ... # other type / class
else if ... # other type / class
else
# it is not a type / class I know
array = something.coerce(self)
return array[0].*(array[1]) # or just return array[0] * array[1]
end
end
end
Таким образом, действительно трудно добавить что-то к Fixnum
метод экземпляра coerce
? Это уже имеет много кода в нем, и мы не можем только добавить несколько строк для улучшения его (но мы будем когда-либо хотеть?)
coerce
в Point
класс довольно универсален, и он работает с *
или +
потому что они являются переходными. Что, если это не является переходным, такой, как будто мы определяем Точку минус Fixnum, чтобы быть:
point = Point.new(100,100)
point - 20 #=> (80,80)
20 - point #=> (-80,-80)
Короткий ответ: посмотрите, как Matrix
это делает.
Идея заключается в том, что coerce
возвращает [equivalent_something, equivalent_self]
, где equivalent_something
- это объект, по сути эквивалентный something
, но умеющий выполнять операции над вашим классом Point
. В либе Matrix
мы создаем Matrix::Scalar
из любого объекта Numeric
, и этот класс умеет выполнять операции над Matrix
и Vector
.
Чтобы ответить на ваши вопросы:
Да, это непосредственно Ruby (проверьте вызовы rb_num_coerce_bin
в источнике), хотя ваши собственные типы тоже должны работать, если вы хотите, чтобы ваш код расширялся другими. Например, если вашей Point#*
передан аргумент, который она не распознает, вы попросите этот аргумент coerce
превратить себя в Point
, вызвав arg.coerce(self)
.
Да, это должен быть массив из двух элементов, такой, что b_equiv, a_equiv = a.coerce(b)
Да. Ruby делает это для встроенных типов, и вы тоже должны создавать свои собственные типы, если хотите быть расширяемыми:
def *(arg)
if (arg не распознан)
self_equiv, arg_equiv = arg.coerce(self)
self_equiv * arg_equiv
end
end
Идея заключается в том, что вы не должны изменять Fixnum#*
. Если он не знает, что делать, например, потому что аргумент является Point
, то он спросит вас, вызвав Point#coerce
.
Переходность (или на самом деле коммутативность) не нужна, потому что оператор всегда вызывается в правильном порядке. Только вызов coerce
временно возвращает полученный и аргумент. Не существует встроенного механизма, обеспечивающего коммутативность операторов типа +
, ==
и т.д...
Если кто-то может придумать краткое, точное и ясное описание для улучшения официальной документации, оставьте комментарий!