Proc 클래스

Proc 클래스

Proc 객체는 코드 블록의 캡슐화예요. 로컬 변수에 저장할 수 있고, 메서드나 다른 Proc에 전달할 수 있으며, 호출할 수 있어요. Proc는 Ruby의 핵심 개념이고 함수형 프로그래밍 기능의 중심이에요.

출처: Ruby 3.3 API

본문

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

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

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

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

생성 (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 }
  • lambda 의미로 Kernel#lambda 메서드로 proc을 만들기(아래 lambda 설명 참고):
lambda1 = lambda {|x| x**2 }
  • Lambda proc 리터럴 구문 사용(역시 lambda 의미로 proc을 만듦):
lambda2 = ->(x) { x**2 }

Lambda와 non-lambda 의미 (Lambda and non-lambda semantics)

Proc은 두 가지 맛으로 나와요: lambda와 non-lambda(일반 proc). 차이점은:

  • lambda에서는 returnbreak가 이 lambda에서 나가는 것을 의미해요.
  • non-lambda proc에서는 return이 감싸는 메서드에서 나가는 것을 의미해요(메서드 밖에서 호출하면 LocalJumpError를 던짐).
  • non-lambda proc에서는 break가 블록이 주어진 메서드에서 나가는 것을 의미해요(메서드가 반환된 후 호출하면 LocalJumpError를 던짐).
  • lambda에서는 인자가 메서드에서와 같은 방식으로 처리돼요: 엄격하고, 인자 수가 맞지 않으면 ArgumentError, 추가 인자 처리는 없어요.
  • 일반 proc은 인자를 더 관대하게 받아요: 없는 인자는 nil로 채워지고, proc에 인자가 여러 개면 단일 Array 인자는 분해되며, 추가 인자에 대해 에러를 내지 않아요.

예시:

# +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+.
# (The block +{ break }+ is given for +m1+ and embraced by +m2+.)
$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.
# (The block +{ next }+ is given for +m1+ and embraced by +m2+.)
$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.)
# (The block is given for stubby lambda syntax and embraced by +m2+.)
$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는 자족적인 함수로 유용해요. 특히 고차 함수의 인자로 유용하고, Ruby 메서드와 정확히 똑같이 동작해요.

Proc은 반복자를 구현하는 데 유용해요.

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

map 안에서 코드 블록은 일반(비-lambda) proc으로 취급돼요. 이는 내부 배열이 인자 쌍으로 분해되고, return이 메서드 test에서 나가는 것을 의미해요. 더 엄격한 lambda로는 그게 불가능해요.

lambda? 인스턴스 메서드로 lambda와 일반 proc을 구분할 수 있어요.

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)

유일한 예외는 동적 메서드 정의예요. 비-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는 body를 비-lambda proc으로 받지만, 정상적인 의미를 가진 메서드를 정의해요.

다른 객체를 proc으로 변환 (Conversion of other objects to procs)

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 객체가 블록에서 생성됐는데 그 Proc 객체가 메서드가 반환될 때까지 살아남으면, returnbreak가 동작할 수 없어요. 그런 경우 returnbreakLocalJumpError를 던져요. 이런 상황의 Proc 객체를 고아(orphaned) 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

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

번호 매겨진 파라미터 (Numbered parameters)

번호 매겨진 파라미터는 짧은 블록을 쓰기 쉽게 하려는 암시적으로 정의된 블록 파라미터예요.

# 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]

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

_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)

충돌을 피하려고 로컬 변수나 메서드 인자를 _1, _2 등으로 이름 짓는 것은 경고를 유발해요.

_1 = 'test'
# warning: `_1' is reserved as numbered parameter

암시적 번호 파라미터를 쓰는 것은 블록의 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 } }
# SyntaxError (numbered parameter is already used in outer block here)
# %w[test me].each { _1.each_char { p _1 } }
#                    ^~

번호 매겨진 파라미터는 Ruby 2.7에서 도입됐어요.

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의 합성인 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, eql?(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

(params,...) → obj, call(params,...) → obj

블록을 호출하며, 블록의 파라미터를 메서드 호출 의미에 가깝게 params의 값들로 설정해요. 블록에서 마지막으로 평가된 표현식의 값을 반환해요.

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?도 참고해요.

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#<<도 참고해요.

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와 연관된 binding을 반환해요.

def fred(param)
  proc {}
end

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

curry → a_proc

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

가변 인자 proc을 커리할 때는 필요한 인자 수가 몇 개인지 결정하기 위해 선택 arity 인자를 공급해야 해요.

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

hash → integer

proc 본문에 해당하는 hash 값을 반환해요.

Object#hash도 참고해요.

inspect

문자열 표현을 반환해요.

lambda? → true or false

Proc 객체가 lambda이면 true, non-lambda이면 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는 비-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면 proc을 lambda로, false면 non-lambda로 취급해요.

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이 키워드 인자로 호출되면, 마지막 hash 인자가 특수 플래그로 표시돼서, 그것이 다른 메서드 호출의 일반 인자 스플랫의 마지막 요소이고 그 메서드 호출이 명시적 키워드나 키워드 스플랫을 포함하지 않으면 마지막 요소가 키워드로 해석되도록 해요. 즉, 키워드가 proc을 통해 다른 메서드로 전달돼요.

이는 다른 메서드에 키워드를 위임하는 proc에만, 그리고 Ruby 2.7 이전 버전과의 하위 호환성을 위해서만 써야 해요.

이 메서드는 아마 언젠가 제거될 거예요. 하위 호환성이 유일한 존재 이유니까요. Ruby 2.7 이전 버전에는 없으므로, 호출 전에 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]

이 proc을 담고 있는 Ruby 소스 파일 이름과 줄 번호를 반환하고, 이 proc이 Ruby에서 정의되지 않았다면(native일 때) nil을 반환해요.

to_proc → proc

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

to_s → string

이 proc의 고유 식별자와, proc이 정의된 위치 표시를 반환해요.

(params,...) → obj

블록을 호출하며, 블록의 파라미터를 메서드 호출 의미에 가깝게 params의 값들로 설정해요. 블록에서 마지막으로 평가된 표현식의 값을 반환해요.

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?도 참고해요.