Как Ruby решает поиск метода в mixin 'diamond'? - PullRequest
0 голосов
/ 30 апреля 2018

Версия Ruby такая:

% ruby -v
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin17]

Я натолкнулся на мысль, что если мы сделаем миксины в форме «алмаза» в Ruby.

Вот пример:

module M3; end
module M1
  prepend M3
end

module M2
  prepend M3
end

class Base
  include M1
  include M2
end

p Base.ancestors # [Base, M3, M2, M1, Object, Kernel, BasicObject]

Результат [Base, M3, M2, M1, Object, Kernel, BasicObject].

Даже если вы измените тип mixin модуля M2 с include на prepend в классе Base, результат будет таким же:

module M3; end
module M1
  prepend M3
end

module M2
  prepend M3
end

class Base
  include M1
  prepend M2 # <= change mixin type
end

p Base.ancestors # [Base, M3, M2, M1, Object, Kernel, BasicObject]

Результат также [Base, M3, M2, M1, Object, Kernel, BasicObject]. Это выглядит странно для меня.

Как Ruby решает поиск метода в mixin 'diamond'?

Примечание. Я уже понял основы поиска методов в Ruby, https://docs.ruby -lang.org / en / trunk / syntax / finements_rdoc.html # label-Method + Lookup .

Ответы [ 3 ]

0 голосов
/ 30 апреля 2018

Насколько я понимаю, что:

  • include и prepend устанавливает частичное отношение (отношение предка) между соответствующими модулями / классами на каждом шаге и
  • Отношение предка не может быть опровергнуто на более позднем этапе.

Давайте начнем с:

module M1; end
module M2; end
module M3; end
class Base; end

и следуйте каждому шагу. Первые три шага должны быть тривиальными:

Base.ancestors
#=> [Base, Object, Kernel, BasicObject]

M1.prepend M3
M1.ancestors
# => [M3, M1]

M2.prepend M3
M2.ancestors
#=> [M3, M2]

Теперь ваш первый важный шаг - Base.include M1. Это вставит предков M1 (всего [M3, M1] чанка) справа от Base до Object:

Base.include M1
Base.ancestors
#=> [Base, M3, M1, Object, Kernel, BasicObject]

Следующий шаг - Base.prepend M2. Это попытается вставить предков M2 (всего блока [M3, M2]) слева от Base. Но обратите внимание, что это может вызвать противоречивую связь между Base и M3.

Base.prepend M2
Base.ancestors
#=> Cannot be [M3, M2, Base, M3, M1, Object, Kernel, BasicObject]

Поскольку уже было установлено, что M3 отображается справа от Base, лучшее, что он может сделать для размещения [M3, M2], это разместить его справа от Base:

Base.prepend M2
Base.ancestors
#=> [Base, M3, M2, M1, Object, Kernel, BasicObject]

Может показаться, что размещение M2 справа от Base противоречит намерению Base.prepend M2. Но это может быть отменено / изменено, чтобы соответствовать на месте, тогда как уже установленное отношение между Base и M3 не может быть отменено в более позднем месте.

На самом деле, когда нет способа удовлетворить уже установленные отношения, возникает ошибка:

module M4; end
module M5; end
M4.include M5
M5.include M4 #>> ArgumentError: cyclic include detected
0 голосов
/ 30 апреля 2018

Хотя оба существующих ответа здесь довольно логичны и объясняют все великолепно, я бы также оставил ссылку на документацию:

[...] , если этот модуль еще не был добавлен в мод или одного из его предков. [...]
- Module#prepend_features

Акцент мой. Тем не менее, prepend M, а также include Mextend M для предков собственного класса) - все NOOP, если модуль уже найден в цепочке предков.

0 голосов
/ 30 апреля 2018

Здесь может помочь пара вещей. Следует вспомнить поведение по умолчанию include и prepend, так как они добавляют модуль только в том случае, если он еще не был добавлен в этот модуль или одного из его предков.

Далее следует взглянуть на то, что происходит как 2 отдельных шага, а не на выполнение include M1 и prepend M2 все за один раз.

С вашими определениями модулей, как в вопросе, если мы просто имеем:

class Base
  include M1
end

Base.ancestors сейчас [Base, M3, M1, Object, PP::ObjectMixin, Kernel, BasicObject], что, вероятно, то, что вы ожидаете.

Далее, если мы сделаем

class Base
  prepend M2
end

Base.ancestors сейчас [Base, M3, M2, M1, Object, PP::ObjectMixin, Kernel, BasicObject]

Причина в том, что M2 добавляет M3, но M3 уже находится в предках Base, поэтому позиция M3 остается неизменной. Однако, поскольку M2 означает M3, это означает, что M2 должно прийти после M3. В результате M2 появляется после M3, а не в качестве первой записи, даже если к ней добавляется Base.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...