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

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

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


当前回答

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

Def Up(anarg)
  yield(anarg)
end

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

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

当Up方法调用带有参数的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

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

不知道你来自什么语言,但假设它是静态语言,这类事情看起来会很熟悉。这是在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中看到)都非常相似,一旦你仔细思考,应该就会很清楚发生了什么。当您看到块时,通常可以安全地假设方法调用是您想要做的,并且块描述了您想要如何做。

我有时会这样使用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时,它被传递给块变量以处理请求。

在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