Ruby中的块和产量

Mat*_*iby 264 ruby block

我试图理解块以及yield它们如何在Ruby中工作.

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

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

Osc*_*Ryz 375

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

在Ruby中,方法可以接收代码块以执行任意代码段.

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

例如,这非常方便迭代列表或提供自定义算法.

请看以下示例:

我将定义一个Person用名称初始化的类,并提供一个do_with_name方法,在调用时,只将name属性传递给接收到的块.

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

    def do_with_name 
        yield( @name ) 
    end
end
Run Code Online (Sandbox Code Playgroud)

这将允许我们调用该方法并传递任意代码块.

例如,要打印我们要做的名称:

person = Person.new("Oscar")

#invoking the method passing a block
person.do_with_name do |name|
    puts "Hey, his name is #{name}"
end
Run Code Online (Sandbox Code Playgroud)

会打印:

Hey, his name is Oscar
Run Code Online (Sandbox Code Playgroud)

注意,块接​​收一个名为变量的变量作为参数name(NB你可以随意调用这个变量,但调用它是有意义的name).当代码调用时,yield它将使用值填充此参数@name.

yield( @name )
Run Code Online (Sandbox Code Playgroud)

我们可以提供另一个块来执行不同的操作.例如,反转名称:

#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"
Run Code Online (Sandbox Code Playgroud)

我们使用完全相同的方法(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"]
Run Code Online (Sandbox Code Playgroud)

或者,我们也可以提供自定义排序算法,例如基于字符串大小:

 days.sort do |x,y|
    x.size <=> y.size
 end

=> ["monday", "friday", "tuesday", "thursday", "wednesday"]
Run Code Online (Sandbox Code Playgroud)

我希望这能帮助你更好地理解它.

顺便说一句,如果该块是可选的,您应该将其称为:

yield(value) if block_given?
Run Code Online (Sandbox Code Playgroud)

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

  • 因此,在Javascripty术语中,它是一种将回调传递给给定方法并调用它的标准方法.感谢您的解释! (5认同)
  • 对不起,这个名字是用`"Oscar"`初始化的实例变量(答案中不是很清楚) (2认同)

mae*_*ics 24

在Ruby中,方法可以检查它们是否以除了普通参数之外还提供块的方式被调用.通常,这是使用该block_given?方法完成的,但您也可以通过在&最终参数名称前添加一个&符号()来将该块称为显式Proc .

如果使用块调用方法,则该方法可以yield根据需要使用一些参数控制块(调用块).请考虑以下示例方法:

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 =)
Run Code Online (Sandbox Code Playgroud)

或者,使用特殊块参数语法:

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 =)
Run Code Online (Sandbox Code Playgroud)


the*_*eIV 22

很可能有人会在这里提供一个真正详细的答案,但我总是发现Robert Sosinski的这篇文章是对块,过程和lambdas之间微妙之处的一个很好的解释.

我应该补充一点,我相信我链接的帖子特定于ruby 1.8.ruby 1.9中的一些内容已经发生了变化,例如块变量是块的本地变量.在1.8中,你会得到如下内容:

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"
Run Code Online (Sandbox Code Playgroud)

而1.9会给你:

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"
Run Code Online (Sandbox Code Playgroud)

我在这台机器上没有1.9,所以上面可能有错误.


Mat*_*ggs 13

我想补充一下为什么你会按照已经很好的答案做事情.

不知道你来自哪种语言,但假设它是一种静态语言,这种事情看起来很熟悉.这是您在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();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

忽略整个流链接的东西,这个想法就是这个

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

这就是你在ruby中的表现

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

完全不同.打破这一个

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

在这里,您基本上将其委托给另一个类,而不是处理第一步和第二步.正如您所看到的,这会大大降低您必须编写的代码量,从而使事情更容易阅读,并减少内存泄漏或文件锁定无法清除的可能性.

现在,它不像你不能在java中做类似的东西,事实上,人们已经做了几十年了.它被称为战略模式.区别在于没有块,对于像文件示例这样简单的东西,由于需要编写的类和方法的数量,策略变得过度.使用块,它是这样一种简单而优雅的方式,没有任何意义,不以这种方式构建代码.

这不是使用块的唯一方法,但是其他的(比如Builder模式,你可以在rails_for api中看到它们在rails中)足够相似,一旦你绕开它,它应该是显而易见的.当你看到块时,通常可以安全地假设方法调用是你想要做的,而块正在描述你想要怎么做.

  • 让我们简化一下:`File.readlines("readfile.rb").each_with_index do | line,index | 把"#{index + 1}:#{line}"end`放到Java家里笑得更厉害. (5认同)
  • @MichaelHampton或者,更好的是:`IO.foreach('readfile.rb').each_with_index {| line,index | 把"#{index}:#{line}"}`(加上没有内存问题) (3认同)

zel*_*nix 12

我发现这篇文章非常有用.特别是,以下示例:

#!/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
Run Code Online (Sandbox Code Playgroud)

哪个应该给出以下输出:

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
Run Code Online (Sandbox Code Playgroud)

所以基本上每次调用yieldruby都会在do块或内部运行代码{}.如果yield提供了参数,则将其作为参数提供给do块.

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

因此,在rails中,您可以编写以下内容:

respond_to do |format|
  format.html { render template: "my/view", layout: 'my_layout' }
end
Run Code Online (Sandbox Code Playgroud)

这将运行respond_to产生do具有(内部)format参数的块的函数.然后,您.html可以在此内部变量上调用该函数,然后生成代码块以运行该render命令.请注意,.html只有在请求的文件格式时才会产生.(技术性:这些函数实际上并block.call没有yield源头看到,但功能基本相同,请参阅此问题进行讨论.)这为函数提供了一种方法,可以执行一些初始化,然后从调用代码中获取输入,然后根据需要进行处理.

或者换句话说,它类似于将匿名函数作为参数然后在javascript中调用它的函数.


小智 7

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

块广泛用于Ruby gem(包括Rails)和编写良好的Ruby代码中.它们不是对象,因此不能分配给变量.

基本语法

块是由{}或do..end包围的一段代码.按照惯例,大括号语法应该用于单行块,而do..end语法应该用于多行块.

{ # This is a single line block }

do
  # This is a multi-line block
end 
Run Code Online (Sandbox Code Playgroud)

任何方法都可以接收块作为隐式参数.块由一个方法中的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.
Run Code Online (Sandbox Code Playgroud)

当达到yield语句时,meditate方法产生对块的控制,执行块内的代码并将控制返回给方法,该方法在yield语句之后立即恢复执行.

当方法包含yield语句时,它期望在调用时接收块.如果未提供块,则在达到yield语句后将抛出异常.我们可以使块可选,并避免引发异常:

def meditate
  puts "Today we will practice zazen."
  yield if block_given? 
end meditate

Output:
Today we will practice zazen. 
Run Code Online (Sandbox Code Playgroud)

无法将多个块传递给方法.每种方法只能接收一个块.

有关详细信息,请访问:http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.html


小智 5

我有时会像这样使用"yield":

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

puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
Run Code Online (Sandbox Code Playgroud)