Proc

Proc

Proc 객체는 코드 블록의 캡슐화예요. 지역 변수에 저장하고, 메서드나 다른 Proc에 넘기고, 호출할 수 있죠. Proc은 Ruby의 필수 개념이고, 함수형 프로그래밍 기능의 핵심이에요.

square = Proc.new {|x| x**2 }

square.call(3)  #=> 9
# shorthands:
square.(3)      #=> 9
square[3]       #=> 9

Proc 객체는 클로저(closure)예요. 즉, 만들어진 당시의 전체 컨텍스트를 기억하고 사용할 수 있어요.

def gen_times(factor)
  Proc.new {|n| n*factor } # remembers the value of factor at the moment of creation
end

times3 = gen_times(3)
times5 = gen_times(5)

times3.call(12)               #=> 36
times5.call(5)                #=> 25
times3.call(times5.call(4))   #=> 60

출처: Ruby 4.0 API

본문

생성(Creation)

Proc을 만드는 방법은 여러 가지가 있어요.

  • Proc 클래스 생성자 사용: proc1 = Proc.new { |x| x ** 2 }
  • Kernel#proc 메서드를 Proc.new의 축약형으로 사용: proc2 = proc { |x| x ** 2 }
  • 코드 블록을 proc 인자로 받기(& 주의): def make_proc(&block) block end; proc3 = make_proc { |x| x ** 2 }
  • Kernel#lambda 메서드로 lambda 의미론의 proc 만들기: lambda1 = lambda { |x| x ** 2 }
  • Lambda proc 리터럴 문법 사용(역시 lambda 의미론): lambda2 = ->(x) { x ** 2 }

Lambda와 non-lambda 의미론

Proc은 두 가지 종류가 있어요: lambda와 non-lambda(일반 proc). 차이는 다음과 같아요.

  • lambda에서 returnbreak는 이 lambda에서 탈출해요.
  • non-lambda proc에서 return은 감싸고 있는(embracing) 메서드에서 탈출해요(메서드 밖에서 호출하면 LocalJumpError 발생).
  • non-lambda proc에서 break는 블록이 주어진 메서드에서 탈출해요(메서드가 반환된 뒤 호출하면 LocalJumpError 발생).
  • lambda에서는 인자가 메서드에서와 같은 방식으로 처리돼요. 엄격해서 인자 수가 맞지 않으면 ArgumentError가 나고, 추가 인자 처리가 없어요.
  • 일반 proc은 인자를 더 관대하게 받아들여요. 빠진 인자는 nil로 채워지고, proc이 여러 인자를 가지면 단일 Array 인자는 분해(deconstruct)되며, 초과 인자에 대해선 오류가 발생하지 않아요.

예시:

# +return+ in non-lambda proc, +b+, exits +m2+.
# (The block +{ return }+ is given for +m1+ and embraced by +m2+.)
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1 { return }; $a << :m2 end; m2; p $a
#=> []

# +break+ in non-lambda proc, +b+, exits +m1+.
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1 { break }; $a << :m2 end; m2; p $a
#=> [:m2]

# +next+ in non-lambda proc, +b+, exits the block.
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1 { next }; $a << :m2 end; m2; p $a
#=> [:m1, :m2]

# Using +proc+ method changes the behavior as follows because
# The block is given for +proc+ method and embraced by +m2+.
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&proc { return }); $a << :m2 end; m2; p $a
#=> []
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&proc { break }); $a << :m2 end; m2; p $a
# break from proc-closure (LocalJumpError)
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&proc { next }); $a << :m2 end; m2; p $a
#=> [:m1, :m2]

# +return+, +break+ and +next+ in the stubby lambda exits the block.
# (+lambda+ method behaves same.)
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&-> { return }); $a << :m2 end; m2; p $a
#=> [:m1, :m2]
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&-> { break }); $a << :m2 end; m2; p $a
#=> [:m1, :m2]
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&-> { next }); $a << :m2 end; m2; p $a
#=> [:m1, :m2]

p = proc {|x, y| "x=#{x}, y=#{y}" }
p.call(1, 2)      #=> "x=1, y=2"
p.call([1, 2])    #=> "x=1, y=2", array deconstructed
p.call(1, 2, 8)   #=> "x=1, y=2", extra argument discarded
p.call(1)         #=> "x=1, y=", nil substituted instead of error

l = lambda {|x, y| "x=#{x}, y=#{y}" }
l.call(1, 2)      #=> "x=1, y=2"
l.call([1, 2])    # ArgumentError: wrong number of arguments (given 1, expected 2)
l.call(1, 2, 8)   # ArgumentError: wrong number of arguments (given 3, expected 2)
l.call(1)         # ArgumentError: wrong number of arguments (given 1, expected 2)

def test_return
  -> { return 3 }.call      # just returns from lambda into method body
  proc { return 4 }.call    # returns from method
  return 5
end

test_return # => 4, return from proc

lambda는 자기완결적인 함수로 유용해요. 특히 고차 함수(higher-order function)의 인자로 쓸 때요. Ruby 메서드처럼 정확히 동작하죠.

Proc은 이터레이터를 구현할 때 유용해요.

def test
  [[1, 2], [3, 4], [5, 6]].map {|a, b| return a if a + b > 10 }
                            #  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
end

map 안에서 그 코드 블록은 일반(non-lambda) proc으로 취급돼요. 즉 내부 배열이 인자 쌍으로 분해되고, return이 메서드 test에서 탈출하죠. 엄격한 lambda로는 이게 불가능해요.

lambda와 일반 proc을 구분하려면 lambda? 인스턴스 메서드를 쓰면 돼요.

lambda 의미론은 보통 proc의 수명 동안 유지돼요. &-분해로 코드 블록이 될 때도요.

p = proc {|x, y| x }
l = lambda {|x, y| x }
[[1, 2], [3, 4]].map(&p) #=> [1, 3]
[[1, 2], [3, 4]].map(&l) # ArgumentError: wrong number of arguments (given 1, expected 2)

유일한 예외는 동적 메서드 정의예요. non-lambda proc을 넘겨 정의해도, 메서드는 여전히 일반적인 인자 검사 의미론을 가져요.

class C
  define_method(:e, &proc {})
end
C.new.e(1,2)       #=> ArgumentError
C.new.method(:e).to_proc.lambda?   #=> true

이 예외 덕분에 메서드는 항상 정상적인 인자 전달 규칙을 갖고, 평소처럼 동작하는 메서드를 정의하는 래퍼를 쉽게 만들 수 있어요.

class C
  def self.def2(name, &body)
    define_method(name, &body)
  end

  def2(:f) {}
end
C.new.f(1,2)       #=> ArgumentError

래퍼 def2body를 non-lambda proc으로 받지만, 정상 의미론을 가진 메서드를 정의해요.

다른 객체를 proc으로 변환

to_proc 메서드를 구현한 객체는 & 연산자로 proc으로 변환되고, 따라서 이터레이터에서 쓸 수 있어요.

class Greeter
  def initialize(greeting)
    @greeting = greeting
  end

  def to_proc
    proc {|name| "#{@greeting}, #{name}!" }
  end
end

hi = Greeter.new("Hi")
hey = Greeter.new("Hey")
["Bob", "Jane"].map(&hi)    #=> ["Hi, Bob!", "Hi, Jane!"]
["Bob", "Jane"].map(&hey)   #=> ["Hey, Bob!", "Hey, Jane!"]

Ruby 코어 클래스 중 Symbol, Method, Hash가 이 메서드를 구현해요.

:to_s.to_proc.call(1)           #=> "1"
[1, 2].map(&:to_s)              #=> ["1", "2"]

method(:puts).to_proc.call(1)   # prints 1
[1, 2].each(&method(:puts))     # prints 1, 2

{test: 1}.to_proc.call(:test)       #=> 1
%i[test many keys].map(&{test: 1})  #=> [1, nil, nil]

고아 Proc(Orphaned Proc)

블록에서 returnbreak는 메서드에서 탈출해요. 만약 그 블록에서 생성된 Proc 객체가 메서드가 반환될 때까지 살아남으면, returnbreak는 동작할 수 없어요. 이런 경우 returnbreakLocalJumpError를 발생시켜요. 이런 상황의 Proc 객체를 고아 Proc 객체라고 불러요.

탈출할 메서드가 returnbreak에서 다르다는 점에 유의하세요. break에는 고아지만 return에는 고아가 아닌 상황이 있을 수 있어요.

def m1(&b) b.call end; def m2(); m1 { return } end; m2 # ok
def m1(&b) b.call end; def m2(); m1 { break } end; m2 # ok

def m1(&b) b end; def m2(); m1 { return }.call end; m2 # ok
def m1(&b) b end; def m2(); m1 { break }.call end; m2 # LocalJumpError

def m1(&b) b end; def m2(); m1 { return } end; m2.call # LocalJumpError
def m1(&b) b end; def m2(); m1 { break } end; m2.call # LocalJumpError

lambda에서는 returnbreak가 블록 자체에서 탈출하므로, lambda는 고아가 될 수 없어요.

익명 블록 파라미터

짧은 블록을 간단히 쓰려고 Ruby는 두 종류의 익명 파라미터를 제공해요: it(단일 파라미터)와 번호 붙은 것들 _1, _2 등.

# Explicit parameter:
%w[test me please].each { |str| puts str.upcase } # prints TEST, ME, PLEASE
(1..5).map { |i| i**2 } # => [1, 4, 9, 16, 25]

# it:
%w[test me please].each { puts it.upcase } # prints TEST, ME, PLEASE
(1..5).map { it**2 } # => [1, 4, 9, 16, 25]

# Numbered parameter:
%w[test me please].each { puts _1.upcase } # prints TEST, ME, PLEASE
(1..5).map { _1**2 } # => [1, 4, 9, 16, 25]

it

it은 위에서 본 것처럼 명시적 파라미터가 정의되지 않았을 때 블록 안에서 사용할 수 있는 이름이에요.

%w[test me please].each { puts it.upcase } # prints TEST, ME, PLEASE
(1..5).map { it**2 } # => [1, 4, 9, 16, 25]

it은 "소프트 키워드(soft keyword)"예요. 예약어가 아니어서 메서드와 지역 변수의 이름으로 쓸 수 있어요.

it = 5 # no warnings
def it(&block) # RSpec-like API, no warnings
   # ...
end

it을 암시적 파라미터로 쓰는 블록 안에서조차 지역 변수로 쓸 수 있어요(분명히 혼란스러운 스타일이지만요).

[1, 2, 3].each {
  # takes a value of implicit parameter "it" and uses it to
  # define a local variable with the same name
  it = it**2
  p it
}

명시적 파라미터가 정의된 블록에서 it을 쓰면 예외가 발생해요.

[1, 2, 3].each { |x| p it }
# syntax error found (SyntaxError)
# [1, 2, 3].each { |x| p it }
#                        ^~ 'it' is not allowed when an ordinary parameter is defined

하지만 지역 이름(변수나 메서드)을 쓸 수 있으면 그것이 사용돼요.

it = 5
[1, 2, 3].each { |x| p it }
# Prints 5, 5, 5

it을 쓰는 블록은 중첩될 수 있어요.

%w[test me].each { it.each_char { p it } }
# Prints "t", "e", "s", "t", "m", "e"

it을 쓰는 블록은 하나의 파라미터를 가진 것으로 간주돼요.

p = proc { it**2 }
l = lambda { it**2 }
p.parameters     # => [[:opt]]
p.arity          # => 1
l.parameters     # => [[:req]]
l.arity          # => 1

번호 붙은 파라미터(Numbered parameters)

번호 붙은 파라미터는 블록 파라미터를 암시적으로 이름 짓는 또 다른 방법이에요. it과 달리 한 블록에서 여러 파라미터를 참조할 수 있어요.

%w[test me please].each { puts _1.upcase } # prints TEST, ME, PLEASE
{a: 100, b: 200}.map { "#{_1} = #{_2}" } # => "a = 100", "b = 200"

_1부터 _9까지의 파라미터 이름이 지원돼요.

[10, 20, 30].zip([40, 50, 60], [70, 80, 90]).map { _1 + _2 + _3 }
# => [120, 150, 180]

다만 현명하게 쓰는 게 좋아요. 아마 _1, _2로 제한하고 한 줄 블록에만 쓰길 권해요.

번호 붙은 파라미터는 명시적으로 이름붙인 파라미터와 함께 쓸 수 없어요.

[10, 20, 30].map { |x| _1**2 }
# SyntaxError (ordinary parameter is defined)

번호 붙은 파라미터는 it과 섞을 수도 없어요.

[10, 20, 30].map { _1 + it }
# SyntaxError: 'it' is not allowed when a numbered parameter is already used

충돌을 피하려고 지역 변수나 메서드 인자를 _1, _2등으로 이름 짓는 것도 오류를 일으켜요.

  _1 = 'test'
# ^~ _1 is reserved for numbered parameters (SyntaxError)

암시적 번호 붙은 파라미터를 쓰면 블록의 arity에 영향을 줘요.

p = proc { _1 + _2 }
l = lambda { _1 + _2 }
p.parameters     # => [[:opt, :_1], [:opt, :_2]]
p.arity          # => 2
l.parameters     # => [[:req, :_1], [:req, :_2]]
l.arity          # => 2

번호 붙은 파라미터를 쓰는 블록은 중첩될 수 없어요.

%w[test me].each { _1.each_char { p _1 } }
# numbered parameter is already used in outer block (SyntaxError)
# %w[test me].each { _1.each_char { p _1 } }
#                    ^~

Public Class Methods

new {|...| block } → a_proc

현재 컨텍스트에 바인딩된 새 Proc 객체를 만들어요.

proc = Proc.new { "hello" }
proc.call   #=> "hello"

블록 없이 호출하면 ArgumentError를 발생시켜요.

Proc.new    #=> ArgumentError

Public Instance Methods

prc << g → a_proc

이 proc과 주어진 g의 합성(composition)인 proc을 돌려줘요. 돌려받은 proc은 가변 개수의 인자를 받아, 먼저 g로 호출하고 그 결과로 이 proc을 호출해요.

f = proc {|x| x * x }
g = proc {|x| x + x }
p (f << g).call(2) #=> 16

자세한 설명은 Proc#>>을 참고하세요.

prc == other → true or false

두 proc이 같은 코드 블록에서 만들어졌을 때만 같아요.

def return_block(&block)
  block
end

def pass_block_twice(&block)
  [return_block(&block), return_block(&block)]
end

block1, block2 = pass_block_twice { puts 'test' }
# Blocks might be instantiated into Proc's lazily, so they may, or may not,
# be the same object.
# But they are produced from the same code block, so they are equal
block1 == block2
#=> true

# Another Proc will never be equal, even if the code is the "same"
block1 == proc { puts 'test' }
#=> false

===(other)

또 다른 case 절에서 쓰이는 동등 호출. (call 계열과 같은 동작)

prc >> g → a_proc

이 proc과 주어진 g의 합성인 proc을 돌려줘요. 돌려받은 proc은 가변 개수의 인자를 받아, 이 proc으로 호출하고 그 결과로 g를 호출해요.

f = proc {|x| x * x }
g = proc {|x| x + x }
p (f >> g).call(2) #=> 8

g는 다른 Proc, Method, 또는 call 메서드에 응답하는 어떤 객체든 가능해요.

class Parser
  def self.call(text)
     # ...some complicated parsing logic...
  end
end

pipeline = File.method(:read) >> Parser >> proc { |data| puts "data size: #{data.count}" }
pipeline.call('data.json')

Method#>>Method#<<도 참고하세요.

self[...] → obj

블록을 호출해요. 메서드 호출 의미론에 가깝게 블록의 파라미터를 인자로 설정해요. 블록에서 평가된 마지막 표현식의 값을 돌려줘요.

a_proc = Proc.new {|scalar, *values| values.map {|value| value*scalar } }
a_proc.call(9, 1, 2, 3)    #=> [9, 18, 27]
a_proc[9, 1, 2, 3]         #=> [9, 18, 27]
a_proc.(9, 1, 2, 3)        #=> [9, 18, 27]
a_proc.yield(9, 1, 2, 3)   #=> [9, 18, 27]

prc.()은 주어진 파라미터로 prc.call()을 호출해요. "call"을 숨기기 위한 문법 설탕이죠.

lambda->()로 만든 proc은 파라미터 수가 틀리면 오류가 나요. Proc.newKernel.proc로 만든 proc은 초과 파라미터를 조용히 버리고, 빠진 파라미터는 nil로 설정해요.

a_proc = proc {|a,b| [a,b] }
a_proc.call(1)   #=> [1, nil]

a_proc = lambda {|a,b| [a,b] }
a_proc.call(1)   # ArgumentError: wrong number of arguments (given 1, expected 2)

Proc#lambda?도 참고하세요.

arity → integer

필수 인자의 수를 돌려줘요. 블록이 인자를 안 받도록 선언되면 0을 돌려줘요. 정확히 n개의 인자를 받는 것으로 알려져 있으면 n을 돌려줘요. 선택적 인자가 있으면 -n-1을 돌려줘요(n은 필수 인자 수). 단, lambda가 아니면서 선택적 인자가 유한 개인 블록은 예외로 n을 돌려줘요. 키워드 인자는 하나의 추가 인자로 간주되고, 어떤 키워드 인자라도 필수면 그 인자는 필수가 돼요. 인자 선언이 없는 proc은 인자를 ||로 선언한 블록과 같아요.

proc {}.arity                  #=>  0
proc { || }.arity              #=>  0
proc { |a| }.arity             #=>  1
proc { |a, b| }.arity          #=>  2
proc { |a, b, c| }.arity       #=>  3
proc { |*a| }.arity            #=> -1
proc { |a, *b| }.arity         #=> -2
proc { |a, *b, c| }.arity      #=> -3
proc { |x:, y:, z:0| }.arity   #=>  1
proc { |*a, x:, y:0| }.arity   #=> -2

proc   { |a=0| }.arity         #=>  0
lambda { |a=0| }.arity         #=> -1
proc   { |a=0, b| }.arity      #=>  1
lambda { |a=0, b| }.arity      #=> -2
proc   { |a=0, b=0| }.arity    #=>  0
lambda { |a=0, b=0| }.arity    #=> -1
proc   { |a, b=0| }.arity      #=>  1
lambda { |a, b=0| }.arity      #=> -2
proc   { |(a, b), c=0| }.arity #=>  1
lambda { |(a, b), c=0| }.arity #=> -2
proc   { |a, x:0, y:0| }.arity #=>  1
lambda { |a, x:0, y:0| }.arity #=> -2

binding → binding

prc와 연관된 바인딩을 돌려줘요.

def fred(param)
  proc {}
end

b = fred(99)
eval("param", b.binding)   #=> 99

call(...) → obj

블록을 호출해요. 블록의 파라미터를 인자로 설정해요. 블록에서 평가된 마지막 표현식의 값을 돌려줘요. 예시와 주의사항은 Proc#[]을 참고하세요.

curry → a_proc / curry(arity) → a_proc

커리된(curried) proc을 돌려줘요. 선택적 arity 인자가 주어지면 인자 수를 결정해요. 커리된 proc은 몇몇 인자를 받아요. 충분한 인자가 공급되면 그 인자들을 원래 proc에 전달하고 결과를 돌려줘요. 그렇지 않으면 나머지 인자를 받는 또 다른 커리된 proc을 돌려줘요.

가변 인자를 가진 proc을 커리할 때는 선택적 arity 인자를 줘서, proc이 호출되기 전에 몇 개의 인자가 필요한지 결정해야 해요.

b = proc {|x, y, z| (x||0) + (y||0) + (z||0) }
p b.curry[1][2][3]           #=> 6
p b.curry[1, 2][3, 4]        #=> 6
p b.curry(5)[1][2][3][4][5]  #=> 6
p b.curry(5)[1, 2][3, 4][5]  #=> 6
p b.curry(1)[1]              #=> 1

b = proc {|x, y, z, *w| (x||0) + (y||0) + (z||0) + w.inject(0, &:+) }
p b.curry[1][2][3]           #=> 6
p b.curry[1, 2][3, 4]        #=> 10
p b.curry(5)[1][2][3][4][5]  #=> 15
p b.curry(5)[1, 2][3, 4][5]  #=> 15
p b.curry(1)[1]              #=> 1

b = lambda {|x, y, z| (x||0) + (y||0) + (z||0) }
p b.curry[1][2][3]           #=> 6
p b.curry[1, 2][3, 4]        #=> wrong number of arguments (given 4, expected 3)
p b.curry(5)                 #=> wrong number of arguments (given 5, expected 3)
p b.curry(1)                 #=> wrong number of arguments (given 1, expected 3)

b = lambda {|x, y, z, *w| (x||0) + (y||0) + (z||0) + w.inject(0, &:+) }
p b.curry[1][2][3]           #=> 6
p b.curry[1, 2][3, 4]        #=> 10
p b.curry(5)[1][2][3][4][5]  #=> 15
p b.curry(5)[1, 2][3, 4][5]  #=> 15
p b.curry(1)                 #=> wrong number of arguments (given 1, expected 3)

b = proc { :foo }
p b.curry[]                  #=> :foo

eql?(other) → true or false

두 proc이 같은 코드 블록에서 만들어졌을 때만 같아요. (==와 같은 의미)

hash → integer

proc 본문에 해당하는 해시 값을 돌려줘요. Object#hash도 참고하세요.

inspect

lambda? → true or false

Proc 객체가 lambda이면 true, 아니면 false를 돌려줘요. lambda 여부는 인자 처리와 return·break 동작에 영향을 줘요.

proc으로 생성된 Proc 객체는 추가 인자를 무시해요.

proc {|a,b| [a,b] }.call(1,2,3)    #=> [1,2]

빠진 인자에는 nil을 제공해요.

proc {|a,b| [a,b] }.call(1)        #=> [1,nil]

단일 배열 인자를 펼쳐요.

proc {|a,b| [a,b] }.call([1,2])    #=> [1,2]

lambda로 생성된 Proc 객체에는 그런 트릭이 없어요.

lambda {|a,b| [a,b] }.call(1,2,3)  #=> ArgumentError
lambda {|a,b| [a,b] }.call(1)      #=> ArgumentError
lambda {|a,b| [a,b] }.call([1,2])  #=> ArgumentError

Proc#lambda?는 그 트릭들에 대한 술어예요. 트릭이 하나도 적용되지 않으면 true를 돌려줘요.

lambda {}.lambda?            #=> true
proc {}.lambda?              #=> false

Proc.newproc과 같아요.

Proc.new {}.lambda?          #=> false

lambda, proc, Proc.new& 인자로 주어진 Proc 객체의 트릭을 보존해요.

lambda(&lambda {}).lambda?   #=> true
proc(&lambda {}).lambda?     #=> true
Proc.new(&lambda {}).lambda? #=> true

lambda(&proc {}).lambda?     #=> false
proc(&proc {}).lambda?       #=> false
Proc.new(&proc {}).lambda?   #=> false

& 인자로 생성된 Proc 객체는 트릭을 가져요.

def n(&b) b.lambda? end
n {}                         #=> false

& 인자는 Proc 객체가 & 인자로 주어지면 트릭을 보존해요.

n(&lambda {})                #=> true
n(&proc {})                  #=> false
n(&Proc.new {})              #=> false

메서드에서 변환된 Proc 객체는 트릭이 없어요.

def m() end
method(:m).to_proc.lambda?   #=> true

n(&method(:m))               #=> true
n(&method(:m).to_proc)       #=> true

define_method는 메서드 정의와 동일하게 취급돼요. 정의된 메서드에는 트릭이 없어요.

class C
  define_method(:d) {}
end
C.new.d(1,2)       #=> ArgumentError
C.new.method(:d).to_proc.lambda?   #=> true

define_method는 non-lambda Proc 객체가 주어져도 항상 트릭 없이 메서드를 정의해요. 트릭이 보존되지 않는 유일한 예외예요.

class C
  define_method(:e, &proc {})
end
C.new.e(1,2)       #=> ArgumentError
C.new.method(:e).to_proc.lambda?   #=> true

이 예외 덕분에 메서드는 절대 트릭을 갖지 않고, 평소처럼 동작하는 메서드를 정의하는 래퍼를 쉽게 만들 수 있어요.

class C
  def self.def2(name, &body)
    define_method(name, &body)
  end

  def2(:f) {}
end
C.new.f(1,2)       #=> ArgumentError

래퍼 def2는 트릭 없이 메서드를 정의해요.

parameters(lambda: nil) → array

이 proc의 파라미터 정보를 돌려줘요. lambda 키워드가 제공되고 nil이 아니면, true일 때는 lambda로, false일 때는 non-lambda로 proc을 취급해요.

prc = proc{|x, y=42, *other|}
prc.parameters  #=> [[:opt, :x], [:opt, :y], [:rest, :other]]
prc = lambda{|x, y=42, *other|}
prc.parameters  #=> [[:req, :x], [:opt, :y], [:rest, :other]]
prc = proc{|x, y=42, *other|}
prc.parameters(lambda: true)  #=> [[:req, :x], [:opt, :y], [:rest, :other]]
prc = lambda{|x, y=42, *other|}
prc.parameters(lambda: false) #=> [[:opt, :x], [:opt, :y], [:rest, :other]]

ruby2_keywords → proc

proc이 일반 인자 스플랫을 통해 키워드를 통과시키도록 표시해요. 이 메서드는 인자 스플랫(*args)은 받지만 명시적 키워드나 키워드 스플랫은 안 받는 proc에서만 호출해야 해요. proc에 키워드 인자로 호출되면, 마지막 해시 인자가 특수 플래그로 표시돼서, 다른 메서드 호출의 일반 인자 스플랫의 마지막 요소가 되고 그 메서드 호출이 명시적 키워드나 키워드 스플랫을 포함하지 않으면 그 마지막 요소가 키워드로 해석돼요. 즉, 키워드가 proc을 통해 다른 메서드로 전달돼요.

이 메서드는 키워드를 다른 메서드에 위임하는 proc에만, 그리고 2.7 이전 Ruby 버전과의 하위 호환을 위해서만 사용해야 해요. 이 메서드는 하위 호환만을 위해 존재하므로 언젠가 제거될 가능성이 커요. 2.7 이전 Ruby에는 존재하지 않으므로, 호출하기 전에 proc이 이 메서드에 응답하는지 확인하세요. 그리고 이 메서드가 제거되면 proc이 키워드를 통과시키지 않는 방향으로 동작이 바뀐다는 점도 알아 두세요.

module Mod
  foo = ->(meth, *args, &block) do
    send(:"do_#{meth}", *args, &block)
  end
  foo.ruby2_keywords if foo.respond_to?(:ruby2_keywords)
end

source_location → [String, Integer, Integer, Integer, Integer]

Proc이 정의된 위치를 돌려줘요. 반환 배열은 다음을 담아요.

(1) the Ruby source filename
(2) the line number where the definition starts
(3) the column number where the definition starts
(4) the line number where the definition ends
(5) the column number where the definitions ends

Proc이 Ruby에서 정의되지 않은 경우(즉, 네이티브) nil을 돌려줘요.

to_proc → proc

객체를 Proc 객체로 변환하는 프로토콜의 일부예요. Proc 클래스의 인스턴스는 단순히 자기 자신을 돌려줘요.

to_s → string

이 proc의 고유 식별자와, proc이 정의된 위치 표시를 돌려줘요.

yield(...) → obj

블록을 호출해요. call 등과 같은 동작이에요. 블록에서 평가된 마지막 표현식의 값을 돌려줘요.