1

考虑我有一个 Range 对象, (1..30).class # => Range

现在考虑我试图找到的因素num

num = 30
factors = (1..num).select { |n| num % n == 0 }
factors.class # => Array

对于Ruby 2.3.1Range 对象没有#select,但 Array 对象有。调用如何Range#select生成 Array 对象?

我相信我没有完全理解 Ruby 对象模型。我目前的理解是factors.class.eql? Range应该返回true,而不是false

factors.class.eql? Array # => true

4

2 回答 2

2

Ruby 中的对象模型很简单,单一继承,但能够“混合”模块以添加共享行为。在您的情况下,您使用的select是模块中存在的方法Enumerable。该模块混合成 Array、Hash 和 Range。这给出了这些类方法的实例,例如select. 您可以在此处阅读有关可枚举方法的更多信息:https ://ruby-doc.org/core-2.2.3/Enumerable.html#method-i-select

如果您考虑一下,Range#select返回一个数组是有道理的。您不是从范围中选择连续值吗?您正在选择块返回 true 的任意值,这使得无法返回范围,因此,#select即使在 Hash 或任何其他混入 Enumerable 的类上调用它,也将始终返回一个数组。

更新:

了解 Enumerable 如何从范围返回数组

要实现任何混合的类,Enumerable您只需#each在您的类上定义方法。假设您假设重新实现了 Range:

class Range
  include Enumerable # mixin

  def initialize(first, last)
    @number_range = first.upto last # this is an array of ints
  end

  def each(&block) # this methods returns an enumerable
    @number_range.each &block
  end
end

有了上面我们可以初始化我们假设的范围实例:

@hypo_range = Range.new 1, 10

并在其上调用可枚举的方法:

@hypo_range.any? { |i| i == 5 } # => true
@hypo_range.select &:odd? # => [1,3,5,7,9]

因为您只需要实现#each挂钩到 Enumerable API,Ruby 就知道如何处理它,无论对象的类是什么。这是因为在您的新#each方法中,您已经在遍历数组!Enumerableeach在底层使用您的方法来实现所有其他可枚举的方法,例如any?, select,find等。

#each方法是您告诉 Ruby 如何迭代您的对象集合的地方。一旦 Ruby 知道如何迭代您的对象,结果就已经是一个数组。

Range 的 Rubinius 实现

您可以在此处看到 Range 是通过使用whileto 从值循环first直到它到达该last值并yield在每次迭代时 ing 到块来实现的。该块将结果收集到一个数组中,这就是你如何让数组退出调用Range#select,因为在引擎盖下select使用它。each

https://github.com/rubinius/rubinius/blob/master/core/range.rb#L118

一些资源:

于 2016-10-28T18:29:49.313 回答
1

检查 Range http://ruby-doc.org/core-2.3.1/Range.html的文档

它说包含模块Enumerable。这就是实现map, all?, any?, find,selectinject更多方法的地方。

于 2016-10-28T18:29:26.037 回答