Ruby 的方法、代碼塊、Proc、Lambda 與閉包構成了其強大的函數式程式設計能力。本文系統解析方法定義與呼叫、代碼塊與 Proc/Lambda 轉換、參數傳遞機制、閉包特性與實戰應用模式。
# 基本語法
def method_name(param1, param2 = default_value)
# 方法體
last_expression # 隱式回傳
end
# 呼叫
method_name(arg1, arg2)
method_name arg1, arg2 # 省略括號
| 類型 | 定義位置 | 呼叫方式 | 範例 |
|---|---|---|---|
| 實例方法 | 類別內 | obj.method | def greet; end |
| 類別方法 | 類別內 self. | Class.method | def self.config; end |
| 單例方法 | 特定物件上 | obj.method | def obj.special; end |
# 預設參數
def greet(name = "World")
"Hello, #{name}!"
end
# 可變參數 (陣列)
def sum(*numbers)
numbers.reduce(0, :+)
end
sum(1,2,3) # 6
# 關鍵字參數
def connect(host:, port: 80, timeout: 5)
# host 必填,port/timeout 可選
end
connect(host: "localhost") # OK
connect(port: 8080) # ArgumentError: missing keyword: host
# 雙星號收集關鍵字參數
def flexible(**options)
options # => Hash
end
flexible(a: 1, b: 2) # {:a=>1, :b=>2}
# 區塊參數 (&block)
def with_logging(&block)
puts "Start"
result = block.call
puts "End"
result
end
with_logging { "Hello" }
method=:設值方法 (setter),obj.name = "x" 實際呼叫 name=method?:斷言方法,回傳布林,如 empty?, nil?method!:危險/修改自身方法,如 sort!, save!代碼塊是附著在方法呼叫後的程式碼片段,非物件,無法直接存放於變數。
# 大括號:單行慣例
[1,2,3].each { |x| puts x * 2 }
# do...end:多行慣例
[1,2,3].each do |x|
puts x * 2
end
def twice
yield
yield
end
twice { puts "Hello" }
# Hello
# Hello
# 傳遞參數給代碼塊
def with_user(name)
yield(name.upcase)
end
with_user("alice") { |name| puts "Hi, #{name}!" } # Hi, ALICE!
def flexible
if block_given?
yield
else
"No block"
end
end
flexible # "No block"
flexible { "Hi" } # "Hi"
# Proc.new / proc (同義)
p1 = Proc.new { |x| x * 2 }
p2 = proc { |x| x * 2 }
# lambda / -> (Ruby 1.9+ 刺操作符語法)
l1 = lambda { |x| x * 2 }
l2 = ->(x) { x * 2 } # 參數在箭頭後
l3 = ->(x, y=1) { x + y } # 支援預設參數
| 行為 | Proc | Lambda |
|---|---|---|
| 參數嚴格度 | 寬鬆:多參數忽略、少參數補 nil | 嚴格:參數數量必須匹配,否則 ArgumentError |
| return 語意 | 從定義 Proc 的方法返回 | 從Lambda 本身返回 |
| break 語意 | 跳出定義處的迴圈/方法 | 跳出 Lambda 本身 (需在迴圈中) |
| 自我檢查 | proc.lambda? == false | lambda.lambda? == true |
def proc_return
p = Proc.new { return "from proc" }
p.call
"never reached"
end
def lambda_return
l = -> { return "from lambda" }
l.call
"reached"
end
proc_return # "from proc" (直接從 proc_return 方法返回)
lambda_return # "reached" (lambda 返回後繼續執行)
# 方法接收代碼塊並轉為 Proc (& 參數)
def to_proc(&block)
block # 這是一個 Proc 物件
end
my_proc = to_proc { "hello" }
my_proc.call # "hello"
# 顯式傳遞 Proc 給方法 (& 前綴展開)
def call_proc(p)
p.call
end
call_proc(my_proc) # 直接傳 Proc 物件
call_proc(&my_proc) # & 將 Proc 轉為代碼塊傳遞
# 任何回應 to_proc 的物件都能用 & 展開
class Greeter
def to_proc
->(name) { "Hello, #{name}!" }
end
end
["Alice", "Bob"].map(&Greeter.new) # ["Hello, Alice!", "Hello, Bob!"]
# Symbol#to_proc 經典用法
%w[alice bob].map(&:upcase) # ["ALICE", "BOB"]
閉包 = 函數 + 其捕獲的詞法環境。Ruby 的 Proc/Lambda 都是閉包。
def counter
count = 0
-> { count += 1 } # 捕獲區域變數 count
end
c1 = counter
c1.call # 1
c1.call # 2
c2 = counter
c2.call # 1 (獨立環境)
# 閉包三大特性:
# 1. 變數可在方法外存取 (count 逃脫作用域)
# 2. 環境變數安全隔離 (c1, c2 各自 count)
# 3. 環境狀態持久化 (count 值保留)
def process(data, strategy)
strategy.call(data)
end
upcase = ->(s) { s.upcase }
reverse = ->(s) { s.reverse }
process("hello", upcase) # "HELLO"
process("hello", reverse) # "OLLEH"
class HtmlBuilder
def initialize(&block)
@html = ""
instance_eval(&block) if block_given?
end
def div(content = nil, &block)
@html << ""
@html << (block_given? ? instance_eval(&block) : content)
@html << ""
end
def to_s
@html
end
end
HtmlBuilder.new do
div do
div "Hello"
div "World"
end
end.to_s
# "HelloWorld"
def memoize(&block)
cache = {}
->(key) { cache[key] ||= block.call(key) }
end
fib = memoize do |n|
return n if n <= 1
fib.call(n-1) + fib.call(n-2)
end
fib.call(10) # 55 (快速)
Ruby 的方法是命名的可複用邏輯單元;代碼塊是附著於方法呼叫的隱式片段;Proc與Lambda將代碼塊物件化,前者語意寬鬆類似代碼塊,後者嚴格類似方法;兩者皆為閉包,捕獲定義時的詞法環境。掌握 yield、&block、Proc/lambda、return/break 語意差異,能寫出極具表達力的 DSL、策略模式、建構器與惰性計算等進階模式。