我正在尝试理解块和yield以及它们在Ruby中的工作方式。

如何使用产量?我研究过的许多Rails应用程序都以一种奇怪的方式使用yield。

谁能给我解释一下或者告诉我怎么理解他们?


当前回答

简单地说,yield允许您创建的方法获取和调用块。yield关键字是块中'stuff'将被执行的位置。

其他回答

我想补充一下为什么你会这样做在已经很好的答案上。

不知道你来自什么语言,但假设它是静态语言,这类事情看起来会很熟悉。这是在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();
    }
  }
}

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

初始化需要清理的资源 使用资源 一定要清理干净

这是在ruby中如何做的

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

完全不同。把这个分解一下

告诉File类如何初始化资源 告诉文件类如何处理它 嘲笑那些还在打字的Java家伙;-)

这里,不是处理第一步和第二步,而是将其委托给另一个类。正如您所看到的,这极大地减少了必须编写的代码量,从而使内容更易于阅读,并降低了内存泄漏或文件锁未清除等问题的可能性。

现在,并不是说你不能在java中做类似的事情,事实上,人们已经做了几十年了。这被称为策略模式。不同之处在于,如果没有块,对于像文件这样简单的例子,策略就会因为需要编写的类和方法的数量而变得多余。使用块,这是一种简单而优雅的方式,不以这种方式构建代码是没有任何意义的。

这并不是使用块的唯一方式,但是其他的(比如Builder模式,你可以在rails中的form_for api中看到)都非常相似,一旦你仔细思考,应该就会很清楚发生了什么。当您看到块时,通常可以安全地假设方法调用是您想要做的,并且块描述了您想要如何做。

很可能有人会在这里提供一个真正详细的答案,但我一直认为Robert Sosinski的这篇文章对block, procs和lambdas之间的微妙之处进行了很好的解释。

我应该补充一点,我所链接的这篇文章是专门针对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,所以上面可能有一个错误。

在Ruby中,方法可以检查调用方法时是否在正常参数之外提供了块。通常这是使用block_given?方法,但你也可以通过在最终参数名前加上&来将该块引用为显式Proc。

如果一个方法是用块调用的,那么如果需要的话,该方法可以通过一些参数将控制权交给块(调用块)。考虑这个示例方法,它演示了:

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 =)

我有时会这样使用yield:

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

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

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

Def Up(anarg)
  yield(anarg)
end

您可以创建一个方法“Up”,它被分配了一个参数。你现在可以将这个参数赋值给yield,它将调用并执行一个相关的块。您可以在参数列表之后分配块。

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

当Up方法调用带有参数的yield时,它被传递给块变量以处理请求。