ブロック・Proc・Lambda
Ruby
yield・block_given?・クロージャ
ブロックと yield
ブロックの渡し方・yield・block_given?
# ブロックを受け取るメソッド
def greet(name)
result = yield(name) if block_given?
puts result || "こんにちは、#{name}!"
end
greet('Alice') # 'こんにちは、Alice!'
greet('Alice') { |n| "Hello, #{n}!" } # 'Hello, Alice!'
# yield の値
def transform(value)
block_given? ? yield(value) : value
end
transform(5) # 5
transform(5) { |x| x * 2 } # 10
# 明示的なブロック引数(&block)
def run_twice(&block)
block.call(1)
block.call(2)
end
run_twice { |n| puts n } # 1, 2
# ブロックの受け渡し
def apply(arr, &block)
arr.map(&block)
end
apply([1,2,3]) { |x| x ** 2 } # [1,4,9]
# Proc に変換して渡す
doubler = proc { |x| x * 2 }
[1,2,3].map(&doubler) # [2,4,6]
# シンボルの to_proc
['a','b','c'].map(&:upcase) # ['A','B','C']
# & は to_proc を呼ぶ → :upcase.to_proc → { |x| x.upcase }
[1,-2,3,-4].select(&:positive?) # [1,3]Proc と Lambda
作成方法・引数チェック・return の違い
# Proc の作成
p1 = Proc.new { |x| x * 2 }
p2 = proc { |x| x * 2 } # 短縮形
p1.call(5) # 10
p1.(5) # 10(短縮記法)
p1[5] # 10([]でも呼べる)
# Lambda の作成
l1 = lambda { |x| x * 2 }
l2 = ->(x) { x * 2 } # stabby lambda(Ruby 1.9+)
l3 = ->(x, y = 0) { x + y } # デフォルト引数
l1.call(5) # 10
l1.lambda? # true
# Proc vs Lambda の主な違い
# 1. 引数チェック
p = proc { |a, b| [a, b] }
l = lambda{ |a, b| [a, b] }
p.call(1) # [1, nil](余分・不足を無視)
l.call(1) # ArgumentError(引数数が厳格)
# 2. return の挙動
def test_proc
p = proc { return 'procのreturn' } # メソッドから抜ける
p.call
'ここには到達しない'
end
def test_lambda
l = lambda { return 'lambdaのreturn' } # lambdaからだけ抜ける
l.call
'ここに到達する' # これが返る
end
test_proc # 'procのreturn'
test_lambda # 'ここに到達する'
# カリー化
add = ->(a, b) { a + b }
add5 = add.curry.(5)
add5.(3) # 8
add5.(10) # 15
multiply = ->(a, b, c) { a * b * c }
double = multiply.curry.(2)
double.(3).(4) # 24クロージャとスコープ
変数のキャプチャ・Enumerator・Fiber
# クロージャ(外部変数をキャプチャ)
def make_counter(start = 0)
count = start
increment = -> { count += 1; count }
decrement = -> { count -= 1; count }
value = -> { count }
[increment, decrement, value]
end
inc, dec, val = make_counter(10)
inc.call # 11
inc.call # 12
dec.call # 11
val.call # 11
# Enumerator(外部イテレータ)
enum = [1, 2, 3].each # Enumeratorオブジェクト
enum.next # 1
enum.next # 2
enum.next # 3
# enum.next # StopIteration
# カスタムEnumerator
fib = Enumerator.new do |yielder|
a, b = 0, 1
loop do
yielder << a # yielder.yield(a) と同義
a, b = b, a + b
end
end
fib.first(8) # [0,1,1,2,3,5,8,13]
fib.lazy.select(&:even?).first(5) # [0,2,8,34,144]
# Fiber(コルーチン)
fiber = Fiber.new do
puts 'Step 1'
Fiber.yield
puts 'Step 2'
Fiber.yield
puts 'Step 3'
end
fiber.resume # 'Step 1'
fiber.resume # 'Step 2'
fiber.resume # 'Step 3'