Ruby

ブロック・Proc・Lambda

Ruby

yield・block_given?・クロージャ

ブロックと yield

ブロックの渡し方・yield・block_given?

blocks.rb ruby
# ブロックを受け取るメソッド
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_lambda.rb ruby
# 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

closures.rb ruby
# クロージャ(外部変数をキャプチャ)
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'