Blocks and yields in Ruby
我试图理解块和
如何使用
有人能向我解释或告诉我去哪里了解他们吗?
是的,一开始有点困惑。
在Ruby中,方法可以接收代码块以执行任意代码段。
当一个方法需要一个块时,它通过调用
例如,迭代列表或提供自定义算法非常方便。
举个例子:
我将定义一个用名称初始化的
1 2 3 4 5 6 7 8 9 | class Person def initialize( name ) @name = name end def do_with_name yield( @name ) end end |
这将允许我们调用该方法并传递任意代码块。
例如,要打印名称,我们将执行以下操作:
1 2 3 4 5 6 | person = Person.new("Oscar") #invoking the method passing a block person.do_with_name do |name| puts"Hey, his name is #{name}" end |
号
将打印:
1 | Hey, his name is Oscar |
注意,块作为一个参数接收一个名为
1 | yield( @name ) |
。
我们可以提供另一个块来执行不同的操作。例如,反转名称:
1 2 3 4 5 6 7 8 9 10 11 | #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" |
我们使用了完全相同的方法(
这个例子很简单。更有趣的用法是过滤数组中的所有元素:
1 2 3 4 5 6 7 8 | days = ["monday","tuesday","wednesday","thursday","friday"] # select those which start with 't' days.select do | item | item.match /^t/ end => ["tuesday","thursday"] |
。
或者,我们也可以提供自定义排序算法,例如基于字符串大小:
1 2 3 4 5 | days.sort do |x,y| x.size <=> y.size end => ["monday","friday","tuesday","thursday","wednesday"] |
。
我希望这能帮助你更好地理解它。
顺便说一句,如果块是可选的,您应该这样调用它:
1 | yield(value) if block_given? |
如果不是可选的,只需调用它。
很有可能有人会在这里提供一个真正详细的答案,但我一直认为罗伯特·索辛斯基的这篇文章是对街区、procs&lambdas之间微妙之处的一个很好的解释。
我应该补充一点,我相信我链接到的帖子特定于Ruby1.8。Ruby1.9中的一些内容发生了变化,比如块变量是块的局部变量。在1.8中,您将得到如下内容:
1 2 3 4 5 6 | >> a ="Hello" =>"Hello" >> 1.times { |a| a ="Goodbye" } => 1 >> a =>"Goodbye" |
鉴于1.9将给您:
1 2 3 4 5 6 | >> a ="Hello" =>"Hello" >> 1.times { |a| a ="Goodbye" } => 1 >> a =>"Hello" |
。
我在这台机器上没有1.9,所以上面可能有一个错误。
在Ruby中,方法可以检查是否以这样的方式调用它们,即除了常规参数之外,还提供了一个块。通常,这是使用
如果一个方法是用一个块调用的,那么如果需要的话,该方法可以用一些参数来控制该块(调用该块)。考虑这个示例方法,它演示了:
1 2 3 4 5 6 7 8 9 10 | 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 =) |
或者,使用特殊的块参数语法:
1 2 3 4 5 6 7 8 9 10 | 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中读取文件的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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 5 6 | File.open("readfile.rb","r") do |infile| while (line = infile.gets) puts"#{counter}: #{line}" counter = counter + 1 end end |
。
完全不同。把这个拆下来
这里,不是处理第一步和第二步,而是将其委托给另一个类。如您所见,这显著降低了您必须编写的代码量,从而使事情更容易读取,并减少了内存泄漏或文件锁未被清除的可能性。
现在,它不像你不能用Java做类似的事情,事实上,人们已经做了几十年了。这就是所谓的战略模式。不同之处在于,如果没有块,对于像文件示例这样简单的东西,由于需要编写的类和方法的数量,策略就会变得过多。对于块来说,这是一种简单而优雅的方法,因此不以这种方式构造代码没有任何意义。
这并不是块的唯一使用方式,但是其他的(比如builder模式,您可以在rails中的api形式中看到)非常相似,一旦您将头绕在这个上面,就会很明显地看到发生了什么。当您看到块时,通常可以安全地假定方法调用是您想要做的,并且块描述了您想要如何做。
我发现这篇文章非常有用。尤其是以下示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 | #!/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 |
。
应给出以下输出:
1 2 3 4 5 6 | 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 |
所以基本上每次调用
对我来说,这是我第一次真正了解
因此,在Rails中,您可以编写以下内容:
1 2 3 | respond_to do |format| format.html { render template:"my/view", layout: 'my_layout' } end |
。
这将运行
或者换句话说,它类似于一个以匿名函数为参数,然后用JavaScript调用它的函数。
在Ruby中,块基本上是可以通过任何方法传递和执行的代码块。块总是与方法一起使用,这些方法通常向它们提供数据(作为参数)。
块广泛用于Ruby Gems(包括Rails)和编写良好的Ruby代码中。它们不是对象,因此不能分配给变量。
基本语法块是由或do..end括起来的一段代码。按照惯例,大括号语法应用于单行块,do..end语法应用于多行块。
1 2 3 4 5 | { # This is a single line block } do # This is a multi-line block end |
任何方法都可以作为隐式参数接收块。块由方法内的yield语句执行。基本语法是:
1 2 3 4 5 6 7 8 9 10 | 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语句时,medite方法将对块产生控制权,执行块内的代码并将控制权返回给方法,该方法将在yield语句之后立即恢复执行。
当一个方法包含yield语句时,它期望在调用时接收一个块。如果未提供块,则在到达yield语句后将引发异常。我们可以使块成为可选的,并避免引发异常:
1 2 3 4 5 6 7 | 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
我有时用"屈服"这个词:
1 2 3 4 5 6 | def add_to_http "http://#{yield}" end puts add_to_http {"www.example.com" } puts add_to_http {"www.victim.com"} |
号
简单地说,生成允许您创建的方法接受并调用块。yield关键字是块中执行"stuff"的位置。
关于收益率,我想说两点。首先,虽然这里有很多答案讨论了将块传递给使用yield的方法的不同方法,但我们还是来讨论控制流。这一点尤其重要,因为您可以对一个块进行多次让步。让我们来看一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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。该yield链接到与调用每个方法的方法关联的块。需要注意的是,每次调用yield时,它都会将控制权返回到客户机代码中每个方法的块中。一旦块执行完毕,它将返回到3.times块。这种情况会发生3次。因此,客户机代码中的块在3个不同的场合被调用,因为yield被显式地调用了3个不同的时间。
我的第二点是关于枚举和收益。用于实例化枚举器类的枚举器,此枚举器对象也响应yield。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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。下次我们调用它时,它将调用下一个收益率等等。
关于枚举有一个有趣的消息。联机文档说明如下:
1 2 3 4 5 6 7 8 9 | 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 |
如果不指定符号作为枚举的参数,Ruby将把枚举器挂接到接收器的每个方法上。有些类没有每个方法,比如字符串类。
1 2 3 4 | str ="I like fruit" enum = str.to_enum enum.next => NoMethodError: undefined method `each' for"I like fruit":String |
。
因此,在使用枚举调用某些对象的情况下,必须明确说明枚举方法是什么。
yield可以用作无名称块以返回方法中的值。考虑以下代码:
1 2 3 | Def Up(anarg) yield(anarg) end |
。
您可以创建一个方法"up",它被分配一个参数。现在可以将此参数赋给yield,yield将调用并执行关联的块。可以在参数列表之后指定块。
1 | Up("Here is a string"){|x| x.reverse!; puts(x)} |
当up方法使用参数调用yield时,它将传递给块变量以处理请求。