Ruby中的块和产量

我想了解块和yield ,以及他们如何在Ruby中工作。

如何使用yield ? 我看过的许多Rails应用程序都使用了一种奇怪的方式。

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

是的,起初有点令人费解。

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

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

例如,这是非常方便的迭代列表或提供自定义algorithm。

以下面的例子:

我要定义一个用名称初始化的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 

注意,块接​​收一个名为variables的参数(注意,你可以调用这个variables,但是这个variables是有意义的)。 当代码调用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"] 

或者,我们也可以提供一个自定义的sortingalgorithm,例如基于string的大小:

  days.sort do |x,y| x.size <=> y.size end => ["monday", "friday", "tuesday", "thursday", "wednesday"] 

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

顺便说一句,如果该块是可选的,你应该这样称呼它:

 yield(value) if block_given? 

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

很有可能有人会在这里提供一个真正详细的答案,但是我总是发现罗伯特·索辛斯基(Robert Sosinski)的这篇文章是对块,过程和lambda之间细微差别的一个很好的解释。

我应该补充说,我相信我链接到的post是特定于ruby1.8。 有些东西在Ruby 1.9中已经改变了,比如块variables是本地块。 在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?完成的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 =) 

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

不知道你来自哪种语言,但假设它是一种静态的语言,这种事情看起来很熟悉。 这是你如何阅读在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(); } } } 

忽略整个stream连锁的事情,这个想法是这样的

  1. 初始化需要清理的资源
  2. 使用资源
  3. 一定要把它清理干净

这是你怎么做的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家伙;-)

在这里,不是处理第一步和第二步,而是基本上将其委托给另一个class级。 正如你所看到的那样,这大大减less了你必须编写的代码量,这使得事情更容易阅读,并且减less了内存泄漏或文件locking没有被清除的可能性。

现在,它不像你在java中做类似的事情,事实上,人们已经做了几十年了。 这就是所谓的战略模式。 不同的是,没有块,对于像文件例子那样简单的东西,由于需要编写的类和方法的数量,策略变得过度。 对于块来说,这是一种简单而优雅的方式,因此没有必要以这种方式构造代码。

这不是使用块的唯一方式,但其他(如Builder模式,可以在rails中的form_for api中看到)足够相似,以至于一旦您将头部缠绕在此之上,就会发生什么。 当你看到块时,通常认为方法调用是你想要做的,而块描述了你想要做什么。

我发现这篇文章是非常有用的。 特别是下面的例子:

 #!/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 

所以基本上每次调用ruby都会在do块或内部运行代码。 如果提供一个参数来yield那么这将作为参数提供给do块。

对我来说,这是我第一次真正了解这些街区做什么。 它基本上是一种让函数访问内部数据结构的方法,用于迭代或configuration函数。

所以当你在轨道上写下如下:

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

这将运行respond_to函数,该函数产生具有(内部) format参数的do块。 然后调用这个内部variables的.html函数,然后产生代码块来运行render命令。 请注意, .html只会在要求的文件格式下产生。 (技术性:这些函数实际上使用block.call而不是从源代码中看到,但function本质上是一样的,请参阅这个问题进行讨论。)这为函数提供了一种方法来执行一些初始化,然后从调用代码,然后进行处理,如果需要的话。

或者换一种说法,它类似于以匿名函数作为参数的函数,然后在javascript中调用它。

我有时候会这样使用“yield”

 def add_to_http "http://#{yield}" end puts add_to_http { "www.example.com" } puts add_to_http { "www.victim.com"} 

产量,简单来说,允许你创build的方法来调用块。 yield关键字特别是块中的“stuff”将被执行的位置。

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

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

基本语法

块是由{}或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语句时,期望在调用的时候接收一个block。 如果没有提供块,则在yield语句到达时抛出exception。 我们可以使该块可选,避免引发exception:

 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

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

 Def Up(anarg) yield(anarg) end 

你可以创build一个方法“向上”,它被分配了一个参数。 你现在可以分配这个参数来产生哪个将会调用和执行一个关联的块。 您可以在参数列表之后分配块。

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

当Up方法使用参数调用yield时,会传递给blockvariables来处理请求。