Ruby如何在' diamond'中解决方法查找问题?混入?

时间:2018-04-30 13:55:48

标签: ruby

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]

即使您在M2类中将模块include的mixin类型从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如何在&#39; diamond&#39;中解决方法查找问题? MIXIN?

注意)我已经在Ruby中了解了方法查找的基础知识,https://docs.ruby-lang.org/en/trunk/syntax/refinements_rdoc.html#label-Method+Lookup

3 个答案:

答案 0 :(得分:3)

我的理解是:

  • includeprepend在每个步骤的相关模块/类之间建立了部分关系(祖先关系),
  • 祖先关系不能在后面的步骤中发生矛盾。

让我们从:

开始
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的左侧。但请注意,这会导致BaseM3之间存在矛盾关系。

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

由于已经确定M3出现在Base的右侧,因此放置[M3, M2]的最佳方法是将其置于Base的右侧1}}:

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

M2放在Base的右侧可能会与Base.prepend M2的意图相矛盾。但是可以取消/修改以适应现场,而BaseM3之间已建立的关系不能在以后取消。

事实上,当无法满足已建立的关系时,就会出现错误:

module M4; end
module M5; end
M4.include M5
M5.include M4 #>> ArgumentError: cyclic include detected

答案 1 :(得分:2)

这里有一些可能有用的东西。一种是回忆includeprepend的默认行为,即如果尚未将模块添加到此模块或其中一个祖先中,则只添加该模块。

接下来,我要看一下两个独立的步骤,而不是一次性执行include M1prepend 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预先M3M3已经在Base的祖先中M3的位置保持不变。但是,由于M2取决于M3,这意味着M2必须在 M3之后。结果是M2显示在M3之后,而不是第一个条目,即使它位于Base之前。

答案 2 :(得分:1)

虽然这里的两个现有答案都非常符合逻辑并且解释了一切都很好,但我还是会删除对文档的引用:

  

[...] 如果此模块尚未添加到mod或其祖先之一。 [...]
   - Module#prepend_features

重点是我的。也就是说,include M,以及本地类祖先的extend M(和var myObj = {firstname: "Stevey", printFirstName() { console.log(this.firstname); }} function printNames({printFirstName}) { printFirstName(); } printNames(myObj); )都是NOOP,如果已经在祖先链中找到了模块。