I am trying to understand blocks and yield
and how they work in Ruby.
How is yield
used? Many of the Rails applications I've looked at use yield
in a weird way.
Can someone explain to me or show me where to go to understand them?
是的,一开始有点令人费解。
在 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
在 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 =)
或者,使用特殊的块参数语法:
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 =)
很可能有人会在这里提供真正详细的答案,但我一直发现Robert Sosinski 的这篇文章很好地解释了块、procs 和 lambdas 之间的微妙之处。
我应该补充一点,我相信我链接到的帖子是特定于 ruby 1.8 的。在 ruby 1.9 中有些事情发生了变化,例如块变量是块的本地变量。在 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,所以上面可能有错误。
我发现这篇文章非常有用。特别是以下示例:
#!/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
这将运行respond_to
产生do
带有(内部)format
参数的块的函数。然后,您在此内部变量上调用该.html
函数,这反过来会产生代码块来运行该render
命令。请注意,.html
只有当它是请求的文件格式时才会产生。(技术性:这些函数实际上block.call
并不yield
像您从源代码中看到的那样使用,但功能本质上是相同的,请参阅此问题进行讨论。)这为函数提供了一种方法来执行一些初始化,然后从调用代码中获取输入和然后根据需要进行处理。
或者换一种说法,它类似于将匿名函数作为参数,然后在 javascript 中调用它的函数。
我想在已经很好的答案中补充一下为什么你会以这种方式做事。
不知道你来自什么语言,但假设它是一种静态语言,这种事情看起来很熟悉。这就是你在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();
}
}
}
忽略整个流链接的东西,这个想法是这样的
这就是你在红宝石中的做法
File.open("readfile.rb", "r") do |infile|
while (line = infile.gets)
puts "#{counter}: #{line}"
counter = counter + 1
end
end
天差地别。打破这个
在这里,您基本上将其委托给另一个类,而不是处理第一步和第二步。正如您所看到的,这极大地减少了您必须编写的代码量,这使事情更容易阅读,并减少了诸如内存泄漏或文件锁未清除之类的事情的可能性。
现在,并不是说你不能在 java 中做类似的事情,事实上,人们已经做了几十年了。它被称为策略模式。不同之处在于,如果没有块,对于像文件示例这样简单的东西,由于需要编写的类和方法的数量,策略变得过大了。使用块,这是一种简单而优雅的方式,不以这种方式构建代码没有任何意义。
这不是使用块的唯一方式,但其他方式(如 Builder 模式,您可以在 rails 中的 form_for api 中看到)非常相似,一旦您了解这一点,应该很明显会发生什么。当您看到块时,通常可以安全地假设方法调用是您想要执行的操作,并且块正在描述您想要如何执行它。
在 Ruby 中,块基本上是可以传递给任何方法并由任何方法执行的代码块。块总是与方法一起使用,这些方法通常向它们提供数据(作为参数)。
块广泛用于 Ruby gem(包括 Rails)和编写良好的 Ruby 代码中。它们不是对象,因此不能分配给变量。
块是由 { } 或 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 语句时,它期望在调用时接收一个块。如果没有提供块,一旦到达 yield 语句就会抛出异常。我们可以使块可选并避免引发异常:
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
我有时会像这样使用“产量”:
def add_to_http
"http://#{yield}"
end
puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
Yields,简单地说,就是允许你创建的方法来获取和调用块。yield 关键字特别是块中的“东西”将被执行的地方。
关于产量,我想说明两点。首先,虽然这里的很多答案都讨论了将块传递给使用 yield 的方法的不同方法,但我们也来谈谈控制流。这一点尤其重要,因为您可以为一个块产生 MULTIPLE 时间。我们来看一个例子:
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
当调用 each 方法时,它会逐行执行。现在当我们到达 3.times 块时,这个块将被调用 3 次。每次它调用yield。该产量链接到与调用 each 方法的方法关联的块。需要注意的是,每次调用 yield 时,都会将控制权返回给客户端代码中的 each 方法块。一旦块执行完毕,它就会返回到 3.times 块。这发生了3次。因此,客户端代码中的该块在 3 次不同的情况下被调用,因为 yield 被显式地调用了 3 次。
我的第二点是关于 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"
所以请注意,每次我们使用外部迭代器调用种类时,它只会调用一次 yield。下次我们调用它时,它会调用下一个 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 调用的某些对象,您必须明确说明您的枚举方法将是什么。
Yield可以用作无名块以在方法中返回值。考虑以下代码:
Def Up(anarg)
yield(anarg)
end
您可以创建一个分配了一个参数的方法“Up”。您现在可以将此参数分配给将调用并执行关联块的 yield。您可以在参数列表之后分配块。
Up("Here is a string"){|x| x.reverse!; puts(x)}
当 Up 方法调用 yield 时,带有一个参数,它被传递给块变量来处理请求。