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

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

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


当前回答

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

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

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

例子:

以Person为例,它是一个具有name属性和do_with_name方法的类。当调用该方法时,它将把name属性传递给块。

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

    def do_with_name   # expects a block
        yield( @name ) # invoke the block and pass the `@name` attribute
    end
end

现在可以调用此方法并传递任意代码块。

person = Person.new("Oscar")

# Invoking the method passing a block to print the value
person.do_with_name do |value|
    puts "Got: #{value}"
end

将打印:

Got: Oscar

注意,块接收一个名为value的变量作为参数。当代码调用yield时,它将@name的值作为参数传递。

yield( @name )

相同的方法可以用不同的块调用。

例如反转名称:

reversed_name = ""

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

puts reversed_name

=> "racsO"

其他更有趣的现实生活例子:

数组中的筛选元素:

 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?

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

您可以在您的计算机上使用irb(交互式Ruby Shell)尝试这些示例。

以下是复制/粘贴表单中的所有示例:

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

    def do_with_name   # expects a block
        yield( @name ) # invoke the block and pass the `@name` attribute
    end
end


person = Person.new("Oscar")

# Invoking the method passing a block to print the value
person.do_with_name do |value|
    puts "Got: #{value}"
end


reversed_name = ""

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

puts reversed_name



# Filter elements in an array:    
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]  

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



# Sort by name length:     
days.sort do |x,y|
   x.size <=> y.size
end

其他回答

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

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

This will run the respond_to function which yields the do block with the (internal) format parameter. You then call the .html function on this internal variable which in turn yields the code block to run the render command. Note that .html will only yield if it is the file format requested. (technicality: these functions actually use block.call not yield as you can see from the source but the functionality is essentially the same, see this question for a discussion.) This provides a way for the function to perform some initialisation then take input from the calling code and then carry on processing if required.

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

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

块在Ruby宝石(包括Rails)和编写良好的Ruby代码中被广泛使用。它们不是对象,因此不能赋值给变量。

基本语法

block是由{}或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语句时,冥想方法将控制权交给块,执行块中的代码并将控制权返回给方法,该方法在yield语句之后立即恢复执行。

当一个方法包含yield语句时,它期望在调用时接收一个块。如果没有提供块,一旦到达yield语句,就会抛出异常。我们可以将block设置为可选的,并避免引发异常:

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

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

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

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

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

关于收益率,我想说两点。首先,虽然这里有很多答案讨论了将块传递给使用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

When the each method is invoked, it executes line by line. Now when we get to the 3.times block, this block will be invoked 3 times. Each time it invokes yield. That yield is linked to the block associated with the method that called the each method. It is important to notice that each time yield is invoked, it returns control back to the block of the each method in client code. Once the block is finished executing, it returns back to the 3.times block. And this happens 3 times. So that block in client code is invoked on 3 separate occasions since yield is explicitly called 3 separate times.

我的第二点是关于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" 

请注意,每次我们使用外部迭代器调用types时,它只会调用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方法上。有些类没有each方法,比如String类。

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

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