В Ruby, как на самом деле работает coerce ()?
говорят, что когда у нас есть класс 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)
? Это Рубин автоматически, или это какой-то код внутри*
метод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
' s метод экземпляраcoerce
? В нем уже есть много кода, и мы не можем просто добавить несколько строк, чтобы улучшить его (но мы когда-нибудь захотим?)-
The
coerce
наPoint
класс довольно общий и он работает с*
или+
потому что они являются транзитивными. Что, если он не является транзитивным, например, если мы определите точку минус Fixnum, чтобы быть:point = Point.new(100,100) point - 20 #=> (80,80) 20 - point #=> (-80,-80)
2 ответа:
короткий ответ: проверить как
Matrix
это.идея в том, что
coerce
возвращает[equivalent_something, equivalent_self]
, гдеequivalent_something
является объектом, в основном эквивалентнымsomething
но это знает, как делать операции на вашемPoint
класса. ВMatrix
lib, мы строимMatrix::Scalar
любойNumeric
объект, и этот класс знает, как выполнять операции надMatrix
иVector
.чтобы обратиться к вашему очки:
Да, это Ruby напрямую (проверьте звонки на
rb_num_coerce_bin
источник), хотя ваши собственные типы должны делать тоже, если вы хотите, чтобы ваш код был расширяемым другими. Например, если вашPoint#*
передается аргумент, который он не распознает, вы бы попросили этот аргумент доPoint
по телефонуarg.coerce(self)
.Да, это должен быть массив из 2 элементов, таких, что
b_equiv, a_equiv = a.coerce(b)
да. Ruby делает это для встроенных типов, и Вы тоже должны использовать свои собственные типы, если хотите быть расширяемыми:
def *(arg) if (arg is not recognized) self_equiv, arg_equiv = arg.coerce(self) self_equiv * arg_equiv end end
идея в том, что вы не должны изменять
Fixnum#*
. Если он не знает, что делать, например, если аргументPoint
, то он спросит вас, позвонивPoint#coerce
.транзитивность (или фактически коммутативность) не нужна, потому что оператор всегда звонил в правильном порядке. Это всего лишь призыв к
coerce
который временно возвращает полученный и аргумент. Нет встроенного механизма, который обеспечивает коммутативность операторов типа+
,==
и т. д...если кто-то может придумать краткое, точное и понятное описание для улучшения официальной документации, оставьте комментарий!
Я часто пишу код по этому шаблону при работе с коммутативностью:
class Foo def initiate(some_state) #... end def /(n) # code that handles Foo/n end def *(n) # code that handles Foo * n end def coerce(n) [ReverseFoo.new(some_state),n] end end class ReverseFoo < Foo def /(n) # code that handles n/Foo end # * commutes, and can be inherited from Foo end