目录 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11

官方 Ruby 常见问题解答

如果您希望报告错误或对此常见问题解答提出改进建议,请访问我们的 GitHub 仓库 并开启一个 issue 或 pull request。

迭代器

什么是迭代器?

迭代器是一个接受代码块或 Proc 对象的方法。在源文件中,代码块紧跟在方法调用之后。迭代器用于生成用户自定义的控制结构——尤其是循环。

让我们看一个例子来了解它是如何工作的。迭代器通常用于对集合的每个元素重复执行相同的操作,如下所示:

data = [1, 2, 3]
data.each do |i|
  puts i
end

输出

1
2
3

数组 data 的 each 方法被传递了 do ... end 代码块,并重复执行它。在每次调用时,代码块都会被传递数组的连续元素。

您可以使用 { ... } 来定义代码块,以替代 do ... end

data = [1, 2, 3]
data.each { |i|
  puts i
}

输出

1
2
3

这段代码与上一个示例具有相同的含义。但是,在某些情况下,优先级问题会导致 do ... end{ ... } 的行为有所不同。

foobar a, b do ... end  # foobar is the iterator.
foobar a, b { ... }     # b is the iterator.

这是因为 { ... }do ... end 代码块与前面的表达式结合得更紧密。第一个示例等同于 foobar(a, b) do ... end,而第二个示例是 foobar(a, b { ... })

如何将代码块传递给迭代器?

您只需将代码块放在迭代器调用之后。您还可以通过在引用 Proc 的变量或常量名称前添加 & 来传递 Proc 对象。

如何在迭代器中使用代码块?

此部分或其部分内容可能已过时或需要确认。

有三种方法可以从迭代器方法执行代码块:(1) yield 控制结构;(2) 使用 call 调用 Proc 参数(由代码块创建);以及 (3) 使用 Proc.new 后跟调用。

yield 语句调用代码块,可以选择传递一个或多个参数。

def my_iterator
  yield 1, 2
end

my_iterator {|a, b| puts a, b }

输出

1
2

如果方法定义具有代码块参数(最后一个形参带有 & 前缀),它将接收附加的代码块,并将其转换为 Proc 对象。可以使用 prc.call(args) 调用它。

def my_iterator(&b)
  b.call(1, 2)
end

my_iterator {|a, b| puts a, b }

输出

1
2

Proc.new(或等效的 proclambda 调用),在迭代器定义中使用时,将获取传递给该方法的代码块作为其参数,并从中生成一个过程对象。(proclambda 实际上是同义词。)

[需要更新:lambda 的行为略有不同,并会产生警告 tried to create Proc object without a block。]

def my_iterator
  Proc.new.call(3, 4)
  proc.call(5, 6)
  lambda.call(7, 8)
end

my_iterator {|a, b| puts a, b }

输出

3
4
5
6
7
8

令人惊讶的是,Proc.new 及其变体在任何意义上都不会消耗附加到方法的代码块——每次调用 Proc.new 都会从同一个代码块生成一个新的过程对象。

您可以通过调用 block_given? 来判断方法是否关联了代码块。

不带代码块的 Proc.new 会做什么?

不带代码块的 Proc.new 无法生成过程对象,并且会发生错误。但是,在方法定义中,不带代码块的 Proc.new 意味着在调用方法时存在代码块,因此不会发生错误。

如何并行运行迭代器?

这是 Matz 在 [ruby-talk:5252] 中使用线程的解决方案的采用。

require "thread"

def combine(*iterators)
  queues = []
  threads = []

  iterators.each do |it|
    queue = SizedQueue.new(1)
    th = Thread.new(it, queue) do |i, q|
           send(i) {|x| q << x }
         end
    queues  << queue
    threads << th
  end

  loop do
    ary = []
    queues.each {|q| ary << q.pop }
    yield ary

    iterators.size.times do |i|
      return if !threads[i].status && queues[i].empty?
    end
  end
end

def it1
  yield 1; yield 2; yield 3
end

def it2
  yield 4; yield 5; yield 6
end

combine(:it1, :it2) do |x|
  # x is [1, 4], then [2, 5], then [3, 6]
end