方法与 Blocks
yield/proc/lambda。
方法与 Blocks
这一课你将能做到
- 定义带默认参数、关键字参数、可变参数的方法,理解参数传递是引用传递
- 熟练使用代码块(block)、yield、Proc 和 lambda,说清三者的区别
- 用方法链和 tap/then 写出优雅的 Ruby 流式处理代码
为什么要学
Block 是 Ruby 最具标志性的特性——每个 Ruby 程序员天天写 array.each { ... },但很少有人能清晰地说出 block、Proc、lambda 的区别。DSL(如 RSpec 的 it { ... })和 Rails 的 scope 全都建立在 block 之上。block 理解不透,看高级 Ruby 代码等于看天书。
人话:方法是「命名了的代码块」,block 是「没有名字、直接跟在方法调用后的代码块」。Ruby 的独特之处在于:方法可以把 block 抓过来,用 yield 执行——这让你写出 with_open_file { ... } 这种结构,不用学 Java 的 try-with-resources。
先建立图像
方法 → 容器,给代码命名
│
▼ 调用时可选地附加一段匿名代码
Block → 用 {} 或 do...end 包裹的匿名代码
│
├─ yield:方法里执行附加的 block
├─ Proc:用 &block 捕获为对象,可复用、可传给其他方法
└─ lambda:特殊的 Proc,参数校验更严格
跟着做
步骤 1:定义方法与参数风格
# 普通参数 + 默认值
def greet(name, greeting = "你好")
"#{greeting},#{name}!"
end
puts greet("小明") # => 你好,小明!
puts greet("Lisa", "Hello") # => Hello,Lisa!
# 关键字参数(Ruby 2.0+)——调用时语义明确,不怕顺序换
def create_user(name:, age:, role: "user")
{ name: name, age: age, role: role }
end
puts create_user(name: "Tom", age: 25).inspect
# => {:name=>"Tom", :age=>25, :role=>"user"}
# 可变参数:数组形式
def sum(*numbers)
numbers.inject(:+)
end
puts sum(1, 2, 3, 4) # => 10
puts sum() # => nil(空数组 inject 没初始值返回 nil)
# 双星号 Hash 参数
def log(message, **options)
puts "[#{options[:level] || :info}] #{message}"
puts "标签: #{options[:tags].join(', ')}" if options[:tags]
end
log("启动完成", level: :warn, tags: ["startup", "main"])
# 方法返回最后一个表达式的值,return 可选
def double(n)
n * 2 # 返回值
end
puts double(7) # => 14
步骤 2:Block 与 yield
# yield:方法里执行调用时附加的 block
def with_timer
start = Time.now
yield # 执行 block
elapsed = Time.now - start
puts "耗时 #{elapsed.round(4)} 秒"
end
with_timer do
sleep(0.1)
puts "做了一件大事"
end
# 输出:做了一件大事
# 耗时 0.1001 秒
# yield 传值给 block
def each_twice(array)
array.each { |item| yield item }
end
each_twice([1, 2, 3]) do |n|
puts n * 10
end
# 输出 10, 20, 30
# block_given? 检查调用时是否传了 block
def smart_greet(name)
if block_given?
yield(name)
else
"你好,#{name}"
end
end
puts smart_greet("小明") # => 你好,小明
puts smart_greet("小明") { |n| "哈啰 #{n}" } # => 哈啰 小明