UnboundMethod
UnboundMethod
Ruby는 "객체화된 메서드(objectified method)"의 두 가지 형태를 지원해요. Class와 Method는 특정 객체와 연결된 메서드, 즉 그 객체에 바인딩(bound)된 메서드 객체를 나타내죠. 객체에 바인딩된 메서드 객체는 Object#method으로 만들 수 있어요.
Ruby는 또한 특정 객체와 연결되지 않은 메서드 객체, 즉 언바운드(unbound) 메서드도 지원해요. 이것은 Module#instance_method을 호출하거나, 바인딩된 메서드 객체에서 unbind를 호출해 만들 수 있어요. 두 경우 모두 결과는 UnboundMethod 객체예요.
언바운드 메서드는 어떤 객체에 바인딩한 뒤에만 호출할 수 있어요. 그 객체는 반드시 메서드의 원래 클래스의 kind_of?여야 해요.
class Square
def area
@side * @side
end
def initialize(side)
@side = side
end
end
area_un = Square.instance_method(:area)
s = Square.new(12)
area = area_un.bind(s)
area.call #=> 144
언바운드 메서드는 객체화된 시점의 메서드에 대한 참조예요. 그래서 이후에 기본 클래스가 바뀌어도 언바운드 메서드에는 영향이 없어요.
class Test
def test
:original
end
end
um = Test.instance_method(:test)
class Test
def test
:modified
end
end
t = Test.new
t.test #=> :modified
um.bind(t).call #=> :original
출처: Ruby 3.3 API
본문
각 메서드를 하나씩 살펴볼게요.
==(other_meth) → true or false
두 언바운드 메서드 객체는 같은 메서드 정의를 가리킬 때 같다고 판정해요.
Array.instance_method(:each_slice) == Enumerable.instance_method(:each_slice)
#=> true
Array.instance_method(:sum) == Enumerable.instance_method(:sum)
#=> false, Array redefines the method for efficiency
arity → integer
메서드가 받는 인자 수를 나타내는 값을 돌려줘요. 고정된 개수의 인자를 받는 메서드는 0 이상의 정수를 돌려주고, 가변 인자를 받는 Ruby 메서드는 -n-1을 돌려줘요. 여기서 n은 필수 인자의 수예요. 키워드 인자는 별도의 추가 인자 하나로 간주되는데, 키워드 인자 중 필수인 것이 있으면 그 인자도 필수로 취급돼요. C로 작성된 메서드가 가변 인자를 받으면 -1을 돌려줘요.
class C
def one; end
def two(a); end
def three(*a); end
def four(a, b); end
def five(a, b, *c); end
def six(a, b, *c, &d); end
def seven(a, b, x:0); end
def eight(x:, y:); end
def nine(x:, y:, **z); end
def ten(*a, x:, y:); end
end
c = C.new
c.method(:one).arity #=> 0
c.method(:two).arity #=> 1
c.method(:three).arity #=> -1
c.method(:four).arity #=> 2
c.method(:five).arity #=> -3
c.method(:six).arity #=> -3
c.method(:seven).arity #=> -3
c.method(:eight).arity #=> 1
c.method(:nine).arity #=> 1
c.method(:ten).arity #=> -2
"cat".method(:size).arity #=> 0
"cat".method(:replace).arity #=> 1
"cat".method(:squeeze).arity #=> -1
"cat".method(:count).arity #=> -1
bind(obj) → method
umeth를 obj에 바인딩해요. umeth를 얻은 클래스가 Klass라면, obj.kind_of?(Klass)가 true여야 해요.
class A
def test
puts "In test, class = #{self.class}"
end
end
class B < A
end
class C < B
end
um = B.instance_method(:test)
bm = um.bind(C.new)
bm.call
bm = um.bind(B.new)
bm.call
bm = um.bind(A.new)
bm.call
결과는 이렇게 나와요:
In test, class = C
In test, class = B
prog.rb:16:in `bind': bind argument must be an instance of B (TypeError)
from prog.rb:16
bind_call(recv, args, ...) → obj
umeth를 recv에 바인딩한 다음, 지정된 인자들로 메서드를 호출해요. 이는 umeth.bind(recv).call(args, ...)과 의미적으로 같아요.
clone → new_method
이 메서드의 클론을 돌려줘요.
class A
def foo
return "bar"
end
end
m = A.new.method(:foo)
m.call # => "bar"
n = m.clone.call # => "bar"
eql?(other_meth) → true or false
두 언바운드 메서드 객체는 같은 메서드 정의를 가리킬 때 같다고 판정해요.
Array.instance_method(:each_slice) == Enumerable.instance_method(:each_slice)
#=> true
Array.instance_method(:sum) == Enumerable.instance_method(:sum)
#=> false, Array redefines the method for efficiency
hash → integer
메서드 객체에 대응하는 해시 값을 돌려줘요. Object#hash도 함께 참고하세요.
inspect → string
기본 메서드의 사람이 읽기 좋은 설명을 돌려줘요.
"cat".method(:count).inspect #=> "#<Method: String#count(*)>"
(1..3).method(:map).inspect #=> "#<Method: Range(Enumerable)#map()>"
후자의 경우 메서드 설명은 원래 메서드의 "owner"( Range에 포함된 Enumerable 모듈)를 포함해요. inspect는 가능할 때 메서드 인자 이름(호출 순서)과 소스 위치도 제공해요.
require 'net/http'
Net::HTTP.method(:get).inspect
#=> "#<Method: Net::HTTP.get(uri_or_host, path=..., port=...) <skip>/lib/ruby/2.7.0/net/http.rb:457>"
인자 정의에서 ...는 그 인자가 선택적(기본값이 있음)이라는 뜻이에요. C로 정의된 메서드(언어 코어와 확장)에서는 위치와 인자 이름을 뽑아낼 수 없고, *(임의 개수 인자)나 _(위치 인자 몇 개) 형태의 일반 정보만 제공돼요.
"cat".method(:count).inspect #=> "#<Method: String#count(*)>"
"cat".method(:+).inspect #=> "#<Method: String#+(_)>"
name → symbol
메서드의 이름을 돌려줘요.
original_name → symbol
메서드의 원래 이름을 돌려줘요.
class C
def foo; end
alias bar foo
end
C.instance_method(:bar).original_name # => :foo
owner → class_or_module
이 메서드가 정의된 클래스나 모듈을 돌려줘요. 다시 말해,
meth.owner.instance_methods(false).include?(meth.name) # => true
메서드가 제거/정의되지 않음/재정의되기 전까지는 위 조건이 성립해요(메서드가 private면 instance_methods 대신 private_instance_methods를 사용). Method#receiver도 함께 참고하세요.
(1..3).method(:map).owner #=> Enumerable
parameters → array
이 메서드의 파라미터 정보를 돌려줘요.
def foo(bar); end
method(:foo).parameters #=> [[:req, :bar]]
def foo(bar, baz, bat, &blk); end
method(:foo).parameters #=> [[:req, :bar], [:req, :baz], [:req, :bat], [:block, :blk]]
def foo(bar, *args); end
method(:foo).parameters #=> [[:req, :bar], [:rest, :args]]
def foo(bar, baz, *args, &blk); end
method(:foo).parameters #=> [[:req, :bar], [:req, :baz], [:rest, :args], [:block, :blk]]
source_location → [String, Integer]
이 메서드를 포함하는 Ruby 소스 파일명과 줄 번호를 돌려줘요. 메서드가 Ruby로 정의되지 않았다면(즉 네이티브라면) nil을 돌려줘요.
super_method → method
super를 사용할 때 호출될 슈퍼클래스의 Method를 돌려줘요. 슈퍼클래스에 그 메서드가 없으면 nil을 돌려줘요.
to_s → string
기본 메서드의 사람이 읽기 좋은 설명을 돌려줘요.
"cat".method(:count).inspect #=> "#<Method: String#count(*)>"
(1..3).method(:map).inspect #=> "#<Method: Range(Enumerable)#map()>"
후자의 경우 메서드 설명은 원래 메서드의 "owner"( Range에 포함된 Enumerable 모듈)를 포함해요. to_s는 가능할 때 메서드 인자 이름(호출 순서)과 소스 위치도 제공해요.
require 'net/http'
Net::HTTP.method(:get).inspect
#=> "#<Method: Net::HTTP.get(uri_or_host, path=..., port=...) <skip>/lib/ruby/2.7.0/net/http.rb:457>"
인자 정의에서 ...는 그 인자가 선택적(기본값이 있음)이라는 뜻이에요. C로 정의된 메서드(언어 코어와 확장)에서는 위치와 인자 이름을 뽑아낼 수 없고, *(임의 개수 인자)나 _(위치 인자 몇 개) 형태의 일반 정보만 제공돼요.
"cat".method(:count).inspect #=> "#<Method: String#count(*)>"
"cat".method(:+).inspect #=> "#<Method: String#+(_)>"