在Ruby on Rails中解释迭代器语法

4

我开始学习Ruby on Rails并发现自己被语法所困扰,因此我不得不阅读一些关于Ruby语法的资料。我从http://www.cs.auckland.ac.nz/references/ruby/doc_bundle/Manual/man-1.4/syntax.html了解了这种语法:

method_call do [`|' expr...`|'] expr...end

他们称之为迭代器。我知道迭代器可以在循环中运行,但我不明白我应该如何读取这个迭代器或者这个语法究竟是什么意思。我经常在RoR视频教程中看到它,这些词语听起来很有道理,但实际上我不知道发生了什么。有人能向我解释一下吗?
编辑:例子
respond_to do |format|
    format.json
    format.xml { render :xml => @posts }
end
7个回答

4

方法可以使用“块”这个构造。它们是匿名方法,传递到方法中。

另一种语法是:

method_call { |var| do_something(var) }

基本上,您是说对于迭代中的每个项目,将其命名为“var”并对该项目执行某些操作。该方法只需调用您传递的块作为“yield”项。

这有帮助吗?

编辑:在您的示例中,他们以一种有趣的方式使用了迭代器模式...可能仅将一个format对象传递到您的块中,因此您可以告诉它要处理哪些格式,以及在看到它时要做什么。

换句话说,他们正在使用该模式创建某种DSL,让您配置对哪些内容进行响应。


谢谢,我从来没有想到它是一个匿名函数。我现在理解了语法,但你能解释一下 DSL 吗? - Gio Borje
DSL(特定领域语言)是一种让你用自己的语言声明关于你的领域(在这种情况下,一个网站)信息的工具。在这种情况下,它让你以极其声明式的方式声明你响应的格式。 - Brian Genisio
Ruby DSLs经常使用这个:Rails示例显然是其中之一 - 还可以看看Rake和Capistrano,它们有明确的Ruby DSL示例。 - Tom Morris

2

在迭代器的情况下,将它们视为Java中的接口:您可以在Ruby中执行for循环,但您可能想要迭代的所有对象都应实现“each”方法,该方法接受一个块(即闭包,匿名函数)。

在Ruby中,块被广泛使用。想象一下您有这个数组:

[1, 2, 3, 4, 5, 6].each do |i| puts i.to_s end

在这里,你正在创建数组,然后调用它的“each”方法。你将块传递给它。你可以像这样将其分开:

arr = [1, 2, 3, 4, 5, 6]
string_printer = lambda do |i| puts i.to_s end
arr.each(&string_printer)

这种接口在其他地方也有实现:哈希集合可以让你迭代键值对:

{:name => "Tom", :gender => :male}.each do |key, value| puts key end

do..end可以用大括号替代,写法如下:

[1, 2, 3, 4, 5, 6].each {|i| puts i.to_s }

由于Ruby采用函数式编程,我们可以实现这样的迭代循环:如果您要创建一个需要迭代某些内容的类,也可以实现each方法。例如:
class AddressBook
  attr_accessor :addresses
  def each(&block)
    @addresses.each {|i| yield i }
  end
end

各种类通过这个块模式实现有趣的功能:例如看看String的each_line和each_byte方法。


2
method_call do [`|' expr...`|'] expr...end

不仅限于迭代函数。

在Ruby中,任何方法都可以将块作为参数传递。然后该方法可以调用该块。对于迭代器,该方法的形式如下:

def iter
  for i in [:x,:y,:z]
    yield i
  end
end

如果您使用带有块的iter,它将循环遍历[:x, :y, :z]并将每个元素传递给该块,然后该块可以执行您想要的任何操作。例如,打印它们:
iter { |z| puts z }

你还可以使用这个方法来隐藏初始化和清理步骤,例如打开和关闭文件。例如:File.open。如果File.open是用纯Ruby实现的(它是为了性能而用C实现的),它会执行以下操作。
def File.open filename, opts
  f = File.new filename, opts
  yield f
  f.close
end

因此,您可以使用以下内容
File.open 'foobar', 'w' do |f|
  f.write 'awesome'
end

respond_to 是类似的。它的工作原理如下:(可以在这里查看真正的实现 这里

   def respond_to
     responder = Responder.new(self)
     block.call(responder)
     responder.respond
   end

它创建了一个响应器对象,该对象具有像html这样的方法,可以接受一个块并将其传递给您。这非常方便,因为它让您可以执行以下操作:
def action
  @foo = Foo.new params[:foo]
  respond_to do |format|
    if @foo.save
      format.html { redirect_to foo_path @foo }
      format.xml { render :xml => @foo.to_xml }
    else
      flash[:error] = "Foo could not be saved!"
      format.html { render :new }
      format.xml { render :xml => {:errors => @foo.errors }.to_xml}
    end
  end
end

看看我如何在块内部根据保存更改行为?如果没有它,这样做会更加烦人。


1
<function> do |<temp variable>|
    <code to operate on temp variable>
end

这将创建一个临时的匿名函数,该函数接受一个项目并将其存储在一个临时变量中,然后让其他操作在该项目上执行。该匿名函数被传递到最初指定用于操作由该函数产生的项目的原始<function>中。


1

你看到的是一段代码块,当你第一次看到它时,语法可能有些笨拙。

那么,基本上,使用迭代器可以获得一个“东西”,该“东西”可以重复出现,并且它接收一个块来知道要做什么。

例如,Range类有一个名为“each”的方法,该方法接收要在范围内的每个元素上执行的代码块。

假设您想要打印它:

range = 1..10 #range literal 
range.each {|i|
    puts i
}

代码:{|i| puts i} 是一个块,它表示当这个范围遍历其每个元素时要执行什么操作。另一种语法是您发布的那种:
 range.each do |i|
      puts i 
 end

这些块与迭代器一起使用,但它们不仅限于“迭代”代码,您可以在其他场景中使用它们,例如:

class Person 
  def initialize( with_name ) 
    @name = with_name
  end
  # executes a block 
  def greet 
      yield @name #passes private attribute name to the block 
  end 
end 

p = Person.new "Oscar" 
p.greet { |n|
     puts "Name length = #{n.length}"
     puts "Hello, #{n}"
}

输出:

Name length = 5
Hello, Oscar

所以,与其拥有一个具有固定行为的“greet”方法,使用块让开发人员指定要执行的操作,这对于迭代器非常有帮助,但正如您刚才所见,这并不是唯一的地方。在您的情况下,该块允许您指定在“respond_to”方法中要执行的操作。

0

我认为你可以称之为迭代器,因为通常情况下,块函数会被调用多次。例如:

5.times do |i|
  puts "#{i} "
end

在幕后,以下步骤被执行:

  • 调用对象实例5的方法times,将代码puts "#{i} "传递给一个Proc对象实例。
  • times方法内部,这段代码在循环中被调用,并将当前索引作为参数传递。这就是times可能看起来像的样子(实际上是C语言):

class Fixnum
  def times_2(&block) # Specifying &block as a parameter is optional
    return self unless block_given?
    i = 0
    while(i < self) do
      yield i # Here the proc instance "block" is called
      i += 1
    end
    return self
  end
end

请注意,作用域(即局部变量等)被复制到块函数中:
x = ' '
5.times do { |i| puts "#{i}" + x }

0
你正在阅读的文档已经非常古老了,几乎可以称之为史前时代的产物。如果网页能够积累灰尘,那么这个页面上一定会有厚厚的一层。
请尝试查看ruby-lang网站上的参考资料。此外,Programming Ruby(pickaxe)书籍也是一个必备的参考资料。

我只是在寻找语法指南,而不是“从头开始学习编程”的指南。 - Gio Borje
我了解。如果您还没有看过《Ruby编程语言》章节的鹤嘴书,建议您参阅一下,这里有一份非常易读且全面的语法描述。 - zetetic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接