Ruby中的块和产量

时间:2010-06-18 01:34:40

标签: ruby block

我正在尝试理解块和yield以及它们如何在Ruby中工作。

yield如何使用?我看过的很多Rails应用程序都以奇怪的方式使用yield

有人可以向我解释或告诉我去哪里理解它们吗?

10 个答案:

答案 0 :(得分:363)

是的,一开始有点令人费解。

在Ruby中,方法可能会收到一个代码块,以便执行任意代码段。

当一个方法需要一个块时,它会通过调用yield函数来调用它。

例如,迭代列表或提供自定义算法非常方便。

采用以下示例:

我将定义一个用名称初始化的Person类,并提供一个do_with_name方法,在调用时,只会将name属性传递给接收到的块。

class Person 
    def initialize( name ) 
         @name = name
    end

    def do_with_name 
        yield( @name ) 
    end
end

这将允许我们调用该方法并传递任意代码块。

例如,要打印我们要做的名字:

person = Person.new("Oscar")

#invoking the method passing a block
person.do_with_name do |name|
    puts "Hey, his name is #{name}"
end

会打印:

Hey, his name is Oscar

注意,块接​​收一个名为name的变量作为参数(N.B.你可以随意调用这个变量,但是把它称为name是有意义的)。当代码调用yield时,它会使用@name的值填充此参数。

yield( @name )

我们可以提供另一个块来执行不同的操作。例如,反转名称:

#variable to hold the name reversed
reversed_name = ""

#invoke the method passing a different block
person.do_with_name do |name| 
    reversed_name = name.reverse
end

puts reversed_name

=> "racsO"

我们使用完全相同的方法(do_with_name) - 它只是一个不同的块。

这个例子很简单。更有趣的用法是过滤数组中的所有元素:

 days = ["monday", "tuesday", "wednesday", "thursday", "friday"]  

 # select those which start with 't' 
 days.select do | item |
     item.match /^t/
 end

=> ["tuesday", "thursday"]

或者,我们也可以提供自定义排序算法,例如基于字符串大小:

 days.sort do |x,y|
    x.size <=> y.size
 end

=> ["monday", "friday", "tuesday", "thursday", "wednesday"]

我希望这可以帮助您更好地理解它。

顺便说一句,如果该块是可选的,您应该将其称为:

yield(value) if block_given?

如果不是可选的,只需调用它即可。

答案 1 :(得分:22)

在Ruby中,方法可以检查它们是否以除了普通参数之外还提供块的方式调用。通常,这是使用block_given?方法完成的,但您也可以通过在最终参数名称前添加&符号(&)作为显式Proc来引用该块。

如果使用块调用方法,则方法可以yield使用某些参数控制块(调用块)(如果需要)。请考虑以下示例方法:

def foo(x)
  puts "OK: called as foo(#{x.inspect})"
  yield("A gift from foo!") if block_given?
end

foo(10)
# OK: called as foo(10)
foo(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as foo(123)
# BLOCK: A gift from foo! How nice =)

或者,使用特殊块参数语法:

def bar(x, &block)
  puts "OK: called as bar(#{x.inspect})"
  block.call("A gift from bar!") if block
end

bar(10)
# OK: called as bar(10)
bar(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as bar(123)
# BLOCK: A gift from bar! How nice =)

答案 2 :(得分:22)

很可能有人会在这里提供一个真正详细的答案,但我总是发现罗伯特索辛斯基this post是对块,触发器和触发器之间微妙之处的一个很好的解释。 lambda表达式。

我应该补充一点,我相信我链接的帖子特定于ruby 1.8。 ruby 1.9中的一些内容已经发生了变化,例如块变量是块的本地变量。在1.8中,您将获得以下内容:

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"

而1.9会给你:

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"

我在这台机器上没有1.9,所以上面可能有错误。

答案 3 :(得分:13)

我想补充一下为什么你会按照已经很好的答案做事。

不知道你来自哪种语言,但假设它是一种静态语言,这种事情看起来很熟悉。这是你在java中读取文件的方式

public class FileInput {

  public static void main(String[] args) {

    File file = new File("C:\\MyFile.txt");
    FileInputStream fis = null;
    BufferedInputStream bis = null;
    DataInputStream dis = null;

    try {
      fis = new FileInputStream(file);

      // Here BufferedInputStream is added for fast reading.
      bis = new BufferedInputStream(fis);
      dis = new DataInputStream(bis);

      // dis.available() returns 0 if the file does not have more lines.
      while (dis.available() != 0) {

      // this statement reads the line from the file and print it to
        // the console.
        System.out.println(dis.readLine());
      }

      // dispose all the resources after using them.
      fis.close();
      bis.close();
      dis.close();

    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

忽略整个流链接的事情,这个想法就是这个

  1. 初始化需要清理的资源
  2. 使用资源
  3. 确保清理它
  4. 这是你在ruby中的方法

    File.open("readfile.rb", "r") do |infile|
        while (line = infile.gets)
            puts "#{counter}: #{line}"
            counter = counter + 1
        end
    end
    

    完全不同。打破这一个

    1. 告诉File类如何初始化资源
    2. 告诉文件类如何处理它
    3. 嘲笑仍在打字的java家伙; - )
    4. 这里,您基本上将其委托给另一个类,而不是处理第一步和第二步。正如您所看到的,这会大大降低您必须编写的代码量,从而使事情更容易阅读,并减少内存泄漏或文件锁定无法清除的可能性。

      现在,它不像你不能在java中做类似的东西,事实上,人们已经做了几十年了。它被称为Strategy模式。区别在于没有块,对于像文件示例这样简单的东西,由于需要编写的类和方法的数量,策略变得过度。使用块,它是这样一种简单而优雅的方式,没有任何意义,不会以这种方式构建代码。

      这不是使用块的唯一方法,但是其他的(比如Builder模式,你可以在rails中的form_for api中看到它)足够相似,一旦你包裹你的脑袋,它应该是显而易见的围绕这个。当你看到块时,通常可以安全地假设方法调用是你想要做的,而块正在描述你想要怎么做。

答案 4 :(得分:12)

我发现this article非常有用。特别是,以下示例:

#!/usr/bin/ruby

def test
  yield 5
  puts "You are in the method test"
  yield 100
end

test {|i| puts "You are in the block #{i}"}

test do |i|
    puts "You are in the block #{i}"
end

应该提供以下输出:

You are in the block 5
You are in the method test
You are in the block 100
You are in the block 5
You are in the method test
You are in the block 100

所以基本上每次调用yield时,ruby都会运行do块或{}内的代码。如果向yield提供了参数,那么这将作为参数提供给do块。

对我而言,这是我第一次真正理解do块正在做什么。它基本上是一种允许函数访问内部数据结构的方法,用于迭代或配置函数。

所以当你在rails中写下以下内容时:

respond_to do |format|
  format.html { render template: "my/view", layout: 'my_layout' }
end

这将运行respond_to函数,该函数产生带有(内部)do参数的format块。然后在此内部变量上调用.html函数,然后生成代码块以运行render命令。请注意,.html只有在请求的文件格式时才会生成。 (技术性:这些函数实际上使用block.call而不是yield,您可以从source看到,但功能基本相同,请参阅this question进行讨论。)这提供了函数执行某些初始化的方法,然后从调用代码中获取输入,然后在需要时继续处理。

或换句话说,它类似于将匿名函数作为参数然后在javascript中调用它的函数。

答案 5 :(得分:7)

在Ruby中,块基本上是一块代码,可以传递给任何方法并由其执行。块总是与方法一起使用,这些方法通常将数据提供给它们(作为参数)。

块广泛用于Ruby gem(包括Rails)和编写良好的Ruby代码中。它们不是对象,因此不能分配给变量。

基本语法

块是由{}或do..end包围的一段代码。按照惯例,大括号语法应该用于单行块,而do..end语法应该用于多行块。

{ # This is a single line block }

do
  # This is a multi-line block
end 

任何方法都可以接收块作为隐式参数。块由一个方法中的yield语句执行。基本语法是:

def meditate
  print "Today we will practice zazen"
  yield # This indicates the method is expecting a block
end 

# We are passing a block as an argument to the meditate method
meditate { print " for 40 minutes." }

Output:
Today we will practice zazen for 40 minutes.

当达到yield语句时,meditate方法产生对块的控制,执行块内的代码并将控制返回给方法,该方法在yield语句之后立即恢复执行。

当方法包含yield语句时,它期望在调用时接收块。如果未提供块,则在达到yield语句后将抛出异常。我们可以使块可选,并避免引发异常:

def meditate
  puts "Today we will practice zazen."
  yield if block_given? 
end meditate

Output:
Today we will practice zazen. 

无法将多个块传递给方法。每种方法只能接收一个块。

详情请见:http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.html

答案 6 :(得分:5)

我有时会像这样使用“收益率”:

def add_to_http
   "http://#{yield}"
end

puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}

答案 7 :(得分:4)

简单地说,收益率允许您创建的方法获取和调用块。 yield关键字专门用于执行块中“stuff”的位置。

答案 8 :(得分:1)

关于产量,我想提出两点。首先,尽管这里有很多答案都讨论了将块传递给使用yield的方法的不同方法,但我们还讨论了控制流。这一点特别重要,因为您可以将一个块产生多次。让我们看一个例子:

class Fruit
  attr_accessor :kinds

  def initialize 
    @kinds = %w(orange apple pear banana)
  end

  def each 
    puts 'inside each'
    3.times { yield (@kinds.tap {|kinds| puts "selecting from #{kinds}"} ).sample }
  end  
end

f = Fruit.new
f.each do |kind|
  puts 'inside block'
end    

=> inside each
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block

调用每个方法时,它将逐行执行。现在,当我们到达3.times块时,该块将被调用3次。每次调用yield。该产量链接到与调用each方法的方法相关联的块。重要的是要注意,每次调用yield时,它将控制权返回给客户端代码中每个方法的块。一旦该块完成执行,它将返回到3.times块。这会发生3次。因此,在3个不同的情况下调用客户端代码中的该块,因为yield明确地称为3个不同的时间。

我的第二点是关于enum_for和yield的。 enum_for实例化Enumerator类,并且此Enumerator对象也响应yield。

class Fruit
  def initialize
    @kinds = %w(orange apple)
  end

  def kinds
    yield @kinds.shift
    yield @kinds.shift
  end
end

f = Fruit.new
enum = f.to_enum(:kinds)
enum.next
 => "orange" 
enum.next
 => "apple" 

因此请注意,每次我们使用外部迭代器调用种类时,它只会调用yield一次。下次调用它时,它将调用下一个yield,依此类推。

关于enum_for有一个有趣的花絮。在线文档声明以下内容:

enum_for(method = :each, *args) → enum
Creates a new Enumerator which will enumerate by calling method on obj, passing args if any.

str = "xyz"
enum = str.enum_for(:each_byte)
enum.each { |b| puts b }    
# => 120
# => 121
# => 122

如果未将符号指定为enum_for的参数,ruby会将枚举器挂接到接收方的each方法上。某些类没有String方法,例如String类。

str = "I like fruit"
enum = str.to_enum
enum.next
=> NoMethodError: undefined method `each' for "I like fruit":String

因此,对于某些用enum_for调用的对象,您必须明确说明枚举方法是什么。

答案 9 :(得分:0)

Yield 可以用作无名块来返回方法中的值。请考虑以下代码:

Def Up(anarg)
  yield(anarg)
end

您可以创建一种方法&#34; Up&#34;分配一个参数。您现在可以将此参数分配给yield,该yield将调用并执行关联的块。您可以在参数列表之后分配块。

Up("Here is a string"){|x| x.reverse!; puts(x)}

当Up方法调用yield时,使用参数将其传递给block变量以处理请求。