블록 전달
블록 전달 (Block forwarding)
잡아둔(captured) 블록을 그대로 전달하고 싶을 때가 있어요. 그럴 때는 블록 인자(block argument)를 쓰고, 식 앞에 &를 붙이면 돼요:
def capture(&block)
block
end
def invoke(&block)
block.call
end
proc = capture { puts "Hello" }
invoke(&proc) # prints "Hello"
위 예시에서 invoke는 블록을 받아요. 그런데 invoke는 일반 인자를 받지 않고 블록 인자만 받기 때문에, proc을 그대로 넘길 수는 없어요. 그래서 &를 써서 proc을 진짜 블록 인자로 넘기고 싶다는 걸 명확히 해주는 거예요. 그렇게 하지 않으면:
invoke(proc) # Error: wrong number of arguments for 'invoke' (1 for 0)
yield하는 메서드에 proc을 넘겨줄 수도 있어요:
def capture(&block)
block
end
def twice(&)
yield
yield
end
proc = capture { puts "Hello" }
twice &proc
위 코드는 단순히 이렇게 다시 쓰인 것과 같아요:
proc = capture { puts "Hello" }
twice do
proc.call
end
아니면 &와 -> 문법을 조합할 수도 있어요:
twice &-> { puts "Hello" }
또는:
def say_hello
puts "Hello"
end
twice &->say_hello
출처: Crystal 공식 문서
본문
잡아두지 않은 블록 전달하기 (Forwarding non-captured blocks)
잡아두지 않은(non-captured) 블록을 전달하려면 yield를 써야 해요:
def foo(&)
yield 1
end
def wrap_foo(&)
puts "Before foo"
foo do |x|
yield x
end
puts "After foo"
end
wrap_foo do |i|
puts i
end
# Output:
# Before foo
# 1
# After foo
블록 전달에 &block 문법을 쓸 수도 있어요. 다만 그러려면 최소한 입력 타입을 지정해야 하고, 생성되는 코드가 클로저를 수반해서 더 느려져요:
def foo(&)
yield 1
end
def wrap_foo(&block : Int32 -> _)
puts "Before foo"
foo(&block)
puts "After foo"
end
wrap_foo do |i|
puts i
end
# Output:
# Before foo
# 1
# After foo
yield로 충분하다면 이렇게 블록을 전달하는 건 피하는 게 좋아요. 또, 잡아둔 블록 안에서는 break와 next를 쓸 수 없다는 문제도 있어요. 그래서 &block 전달을 쓰면 다음 코드는 작동하지 않아요:
foo_forward do |i|
break # error
end
요약하면, yield가 연관된 상황에서는 &block 전달을 피하는 게 좋아요.
더 알아보기
- 잡아둔 블록은
&를 붙여 전달하고, 잡아두지 않은 블록은yield로 전달해요. &block전달은 입력 타입을 지정해야 하고 클로저로 인해 더 느려지므로,yield만으로 충분하면 되도록 피하세요.- 잡아둔 블록 안에서는
break와next를 쓸 수 없다는 점을 기억해 두세요.