Skip to content

Ruby 块

在 Ruby 中,方法(methods)将语句组合起来以实现可重用执行。类似地,Ruby 提供了区块(Blocks),它们是 Ruby 表达能力的基础,常用于迭代、回调和资源管理。

  • 区块是一段代码,可以与方法调用关联。
  • 区块通常是匿名的,但可以转换为称为 Proc 的对象。
  • 区块内的代码通常用 do...end(用于多行区块)或花括号 {}(用于单行区块)括起来。
  • 方法使用 yield 关键字来调用关联的区块。
  • 如果预期有区块,可以使用 block_given? 来检查它是否存在。
method_name do |parameter1, parameter2|
# statement1
# statement2
# ...
end
method_name { |parameter| statement }

本教程将引导您通过 yield 调用区块,向区块传递参数,并了解区块如何与方法交互,包括将它们转换为 Proc 对象。

yield 关键字用于方法内部,将控制权转移到与该方法调用关联的区块。我们来看一个例子:

#!/usr/bin/env ruby
def custom_method
puts "Inside the method, before yield."
yield if block_given? # Safely yield only if a block is provided
puts "Inside the method, after yield."
yield if block_given?
end
custom_method do
puts "Inside the block!"
end
puts "--- Calling method without a block ---"
custom_method # This won't raise an error due to block_given?

这将产生以下结果:

Inside the method, before yield.
Inside the block!
Inside the method, after yield.
Inside the block!
--- Calling method without a block ---
Inside the method, before yield.
Inside the method, after yield.

您还可以通过 yield 语句向区块传递参数。然后,区块定义参数来接收这些参数:

#!/usr/bin/env ruby
def greet
yield "Alice", 25
puts "Back in the greet method."
yield "Bob", 30
end
greet do |name, age|
puts "Hello, #{name}! You are #{age} years old."
end

这将产生以下结果:

Hello, Alice! You are 25 years old.
Back in the greet method.
Hello, Bob! You are 30 years old.

在这里,yield "Alice", 25 将 “Alice” 和 25 传递给区块。区块在其参数 |name, age| 中接收这些值。如果区块预期的参数少于 yield 提供的参数,则额外的参数将被忽略。如果它预期的参数多于 yield 提供的参数,则会将 nil 分配给额外的参数。

虽然 yield 隐式地调用传递的区块,但如果方法定义中的最后一个参数前缀有 амперсанд (&),则方法可以显式地将区块捕获为 Proc 对象。然后可以使用其 call 方法调用此 Proc 对象。

#!/usr/bin/env ruby
def execute_block(&my_proc)
if my_proc
puts "Proc received, calling it now."
my_proc.call("Explicitly called")
else
puts "No block was passed."
end
end
execute_block do |message|
puts "Inside the block: #{message}"
end
execute_block # Calling without a block

这将产生以下结果:

Proc received, calling it now.
Inside the block: Explicitly called
No block was passed.

这种机制功能强大,因为它允许存储、传递和稍后调用区块。您还可以使用 Proc.new、lambda 或尖刀 lambda 语法 -> { } 显式创建 Proc 对象。

#!/usr/bin/env ruby
# Using Proc.new
my_proc = Proc.new { |x| puts "Proc.new called with #{x}" }
my_proc.call(10)
# Using lambda
my_lambda = lambda { |x| puts "Lambda called with #{x}" }
my_lambda.call(20)
# Using stabby lambda (preferred for lambdas)
stabby_lambda = ->(x) { puts "Stabby lambda called with #{x}" }
stabby_lambda.call(30)
# Passing a Proc to a method expecting a block
def take_proc_as_block(greeting, &block)
block.call(greeting)
end
greeter_proc = ->(name) { "Hello, #{name}!"}
take_proc_as_block("World", &greeter_proc) # The & converts Proc to block
# If take_proc_as_block used yield, you could pass greeter_proc using `yield greeter_proc`
# and then the method call would be take_proc_as_block("World") { |proc_arg| proc_arg.call("something") }

lambda 比使用 Proc.new 创建的 Proc 具有更严格的参数检查。此外,Proc.new 区块内的 return 会从定义该 Proc 的方法中返回,而 lambda 内部的 return 则从 lambda 本身返回。

Ruby 允许定义 BEGIN 和 END 区块。BEGIN 区块在脚本中的任何其他代码之前执行,甚至在完全解析完成之前。END 区块在程序执行完成后执行,包括任何 at_exit 处理程序。

#!/usr/bin/env ruby
puts "This is the main program execution."
BEGIN {
puts "BEGIN block: Executed first."
}
END {
puts "END block: Executed last."
}
BEGIN {
puts "Another BEGIN block: Order matters for BEGIN blocks."
}
END {
puts "Another END block: Reverse order for END blocks."
}

一个程序可以包含多个 BEGIN 和 END 区块。BEGIN 区块按照它们遇到的顺序执行。END 区块按照它们出现的相反顺序执行。这会产生:

BEGIN block: Executed first.
Another BEGIN block: Order matters for BEGIN blocks.
This is the main program execution.
Another END block: Reverse order for END blocks.
END block: Executed last.

虽然 BEGIN 和 END 区块是 Ruby 的一部分,但在现代应用程序开发中很少使用。它们在独立脚本中更常用于设置或清理任务。对于更复杂的应用程序,首选依赖管理工具和特定于框架的初始化器/终结器。

  • yield vs. &block:yield 更简单,用于直接调用区块。&block 通过将区块转换为 Proc 对象提供更大的灵活性。
  • Proc vs. lambda:主要区别在于参数检查(lambdas 更严格)和 return 行为。
  • block_given?:如果区块是可选的,在使用 yield 之前始终使用此方法,以防止出现 LocalJumpError。

为了更深入地理解,请探索: