class Method
Method объекты создаются с помощью Object#method и связаны с конкретным объектом (не только с классом). Они могут использоваться для вызова метода внутри объекта и в качестве блока, связанного с итератором. Их также можно отвязать от одного объекта (создавая UnboundMethod) и связать с другим.
class Thing
def square(n)
n*n
end
end
thing = Thing.new
meth = thing.method(:square)
meth.call(9) #=> 81
[ 1, 2, 3 ].collect(&meth) #=> [1, 4, 9]
[ 1, 2, 3 ].each(&method(:puts)) #=> prints 1, 2, 3
require 'date'
%w[2017-03-01 2017-03-02].collect(&Date.method(:parse))
#=> [#<Date: 2017-03-01 ((2457814j,0s,0n),+0s,2299161j)>, #<Date: 2017-03-02 ((2457815j,0s,0n),+0s,2299161j)>]
Открытые методы экземпляра
static VALUE
rb_method_compose_to_left(VALUE self, VALUE g)
{
g = to_callable(g);
self = method_to_proc(self);
return proc_compose_to_left(self, g);
} Возвращает proc, который является композицией этого метода и заданного g. Возвращаемый proc принимает переменное количество аргументов, вызывает g с ними, а затем вызывает этот метод с результатом.
def f(x)
x * x
end
f = self.method(:f)
g = proc {|x| x + x }
p (f << g).call(2) #=> 16
static VALUE
method_eq(VALUE method, VALUE other)
{
struct METHOD *m1, *m2;
VALUE klass1, klass2;
if (!rb_obj_is_method(other))
return Qfalse;
if (CLASS_OF(method) != CLASS_OF(other))
return Qfalse;
Check_TypedStruct(method, &method_data_type);
m1 = (struct METHOD *)DATA_PTR(method);
m2 = (struct METHOD *)DATA_PTR(other);
klass1 = method_entry_defined_class(m1->me);
klass2 = method_entry_defined_class(m2->me);
if (!rb_method_entry_eq(m1->me, m2->me) ||
klass1 != klass2 ||
m1->klass != m2->klass ||
m1->recv != m2->recv) {
return Qfalse;
}
return Qtrue;
} Два объекта метода равны, если они связаны с одним и тем же объектом и ссылаются на одно и то же определение метода, и их владельцы являются одним и тем же классом или модулем.
static VALUE
rb_method_call_pass_called_kw(int argc, const VALUE *argv, VALUE method)
{
VALUE procval = rb_block_given_p() ? rb_block_proc() : Qnil;
return rb_method_call_with_block_kw(argc, argv, method, procval, RB_PASS_CALLED_KEYWORDS);
} Вызывает метод с obj в качестве параметра, как call. Это позволяет объекту метода быть целью предложения when в операторе case.
require 'prime' case 1373 when Prime.method(:prime?) # ... end
static VALUE
rb_method_compose_to_right(VALUE self, VALUE g)
{
g = to_callable(g);
self = method_to_proc(self);
return proc_compose_to_right(self, g);
} Возвращает proc, который является композицией этого метода и заданного g. Возвращаемый proc принимает переменное количество аргументов, вызывает этот метод с ними, а затем вызывает g с результатом.
def f(x)
x * x
end
f = self.method(:f)
g = proc {|x| x + x }
p (f >> g).call(2) #=> 8
static VALUE
rb_method_call_pass_called_kw(int argc, const VALUE *argv, VALUE method)
{
VALUE procval = rb_block_given_p() ? rb_block_proc() : Qnil;
return rb_method_call_with_block_kw(argc, argv, method, procval, RB_PASS_CALLED_KEYWORDS);
} Вызывает meth с указанными аргументами, возвращая возвращаемое значение метода, как call.
m = 12.method("+")
m[3] #=> 15
m[20] #=> 32
static VALUE
method_arity_m(VALUE method)
{
int n = method_arity(method);
return INT2FIX(n);
} Возвращает указание на количество аргументов, принимаемых методом. Возвращает неотрицательное целое число для методов, которые принимают фиксированное количество аргументов. Для методов 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
static VALUE
rb_method_call_pass_called_kw(int argc, const VALUE *argv, VALUE method)
{
VALUE procval = rb_block_given_p() ? rb_block_proc() : Qnil;
return rb_method_call_with_block_kw(argc, argv, method, procval, RB_PASS_CALLED_KEYWORDS);
} Вызывает meth с указанными аргументами, возвращая возвращаемое значение метода.
m = 12.method("+")
m.call(3) #=> 15
m.call(20) #=> 32
static VALUE
method_clone(VALUE self)
{
VALUE clone;
struct METHOD *orig, *data;
TypedData_Get_Struct(self, struct METHOD, &method_data_type, orig);
clone = TypedData_Make_Struct(CLASS_OF(self), struct METHOD, &method_data_type, data);
CLONESETUP(clone, self);
RB_OBJ_WRITE(clone, &data->recv, orig->recv);
RB_OBJ_WRITE(clone, &data->klass, orig->klass);
RB_OBJ_WRITE(clone, &data->iclass, orig->iclass);
RB_OBJ_WRITE(clone, &data->me, rb_method_entry_clone(orig->me));
return clone;
} Возвращает клон этого метода.
class A
def foo
return "bar"
end
end
m = A.new.method(:foo)
m.call # => "bar"
n = m.clone.call # => "bar"
static VALUE
rb_method_curry(int argc, const VALUE *argv, VALUE self)
{
VALUE proc = method_to_proc(self);
return proc_curry(argc, argv, proc);
} Возвращает каррированный proc на основе метода. Когда proc вызывается с числом аргументов, меньшим, чем арность метода, возвращается другой каррированный proc. Только когда будет предоставлено достаточно аргументов для удовлетворения сигнатуры метода, метод будет фактически вызван.
Необязательный аргумент arity следует указывать при каррировании методов с переменным числом аргументов, чтобы определить, сколько аргументов необходимо, прежде чем будет вызван метод.
def foo(a,b,c) [a, b, c] end proc = self.method(:foo).curry proc2 = proc.call(1, 2) #=> #<Proc> proc2.call(3) #=> [1,2,3] def vararg(*args) args end proc = self.method(:vararg).curry(4) proc2 = proc.call(:x) #=> #<Proc> proc3 = proc2.call(:y, :z) #=> #<Proc> proc3.call(:a) #=> [:x, :y, :z, :a]
static VALUE
method_eq(VALUE method, VALUE other)
{
struct METHOD *m1, *m2;
VALUE klass1, klass2;
if (!rb_obj_is_method(other))
return Qfalse;
if (CLASS_OF(method) != CLASS_OF(other))
return Qfalse;
Check_TypedStruct(method, &method_data_type);
m1 = (struct METHOD *)DATA_PTR(method);
m2 = (struct METHOD *)DATA_PTR(other);
klass1 = method_entry_defined_class(m1->me);
klass2 = method_entry_defined_class(m2->me);
if (!rb_method_entry_eq(m1->me, m2->me) ||
klass1 != klass2 ||
m1->klass != m2->klass ||
m1->recv != m2->recv) {
return Qfalse;
}
return Qtrue;
} Два объекта метода равны, если они связаны с одним и тем же объектом и ссылаются на одно и то же определение метода, и их владельцы являются одним и тем же классом или модулем.
static VALUE
method_hash(VALUE method)
{
struct METHOD *m;
st_index_t hash;
TypedData_Get_Struct(method, struct METHOD, &method_data_type, m);
hash = rb_hash_start((st_index_t)m->recv);
hash = rb_hash_method_entry(hash, m->me);
hash = rb_hash_end(hash);
return ST2FIX(hash);
} Возвращает хеш-значение, соответствующее объекту метода.
См. также Object#hash.
static VALUE
method_inspect(VALUE method)
{
struct METHOD *data;
VALUE str;
const char *sharp = "#";
VALUE mklass;
VALUE defined_class;
TypedData_Get_Struct(method, struct METHOD, &method_data_type, data);
str = rb_sprintf("#<% "PRIsVALUE": ", rb_obj_class(method));
mklass = data->klass;
if (RB_TYPE_P(mklass, T_ICLASS)) {
/* TODO: I'm not sure why mklass is T_ICLASS.
* UnboundMethod#bind() can set it as T_ICLASS at convert_umethod_to_method_components()
* but not sure it is needed.
*/
mklass = RBASIC_CLASS(mklass);
}
if (data->me->def->type == VM_METHOD_TYPE_ALIAS) {
defined_class = data->me->def->body.alias.original_me->owner;
}
else {
defined_class = method_entry_defined_class(data->me);
}
if (RB_TYPE_P(defined_class, T_ICLASS)) {
defined_class = RBASIC_CLASS(defined_class);
}
if (FL_TEST(mklass, FL_SINGLETON)) {
VALUE v = rb_ivar_get(mklass, attached);
if (data->recv == Qundef) {
rb_str_buf_append(str, rb_inspect(mklass));
}
else if (data->recv == v) {
rb_str_buf_append(str, rb_inspect(v));
sharp = ".";
}
else {
rb_str_buf_append(str, rb_inspect(data->recv));
rb_str_buf_cat2(str, "(");
rb_str_buf_append(str, rb_inspect(v));
rb_str_buf_cat2(str, ")");
sharp = ".";
}
}
else {
rb_str_buf_append(str, rb_inspect(mklass));
if (defined_class != mklass) {
rb_str_catf(str, "(% "PRIsVALUE")", defined_class);
}
}
rb_str_buf_cat2(str, sharp);
rb_str_append(str, rb_id2str(data->me->called_id));
if (data->me->called_id != data->me->def->original_id) {
rb_str_catf(str, "(%"PRIsVALUE")",
rb_id2str(data->me->def->original_id));
}
if (data->me->def->type == VM_METHOD_TYPE_NOTIMPLEMENTED) {
rb_str_buf_cat2(str, " (not-implemented)");
}
// parameter information
{
VALUE params = rb_method_parameters(method);
VALUE pair, name, kind;
const VALUE req = ID2SYM(rb_intern("req"));
const VALUE opt = ID2SYM(rb_intern("opt"));
const VALUE keyreq = ID2SYM(rb_intern("keyreq"));
const VALUE key = ID2SYM(rb_intern("key"));
const VALUE rest = ID2SYM(rb_intern("rest"));
const VALUE keyrest = ID2SYM(rb_intern("keyrest"));
const VALUE block = ID2SYM(rb_intern("block"));
const VALUE nokey = ID2SYM(rb_intern("nokey"));
int forwarding = 0;
rb_str_buf_cat2(str, "(");
for (int i = 0; i < RARRAY_LEN(params); i++) {
pair = RARRAY_AREF(params, i);
kind = RARRAY_AREF(pair, 0);
name = RARRAY_AREF(pair, 1);
// FIXME: in tests it turns out that kind, name = [:req] produces name to be false. Why?..
if (NIL_P(name) || name == Qfalse) {
// FIXME: can it be reduced to switch/case?
if (kind == req || kind == opt) {
name = rb_str_new2("_");
}
else if (kind == rest || kind == keyrest) {
name = rb_str_new2("");
}
else if (kind == block) {
name = rb_str_new2("block");
}
else if (kind == nokey) {
name = rb_str_new2("nil");
}
}
if (kind == req) {
rb_str_catf(str, "%"PRIsVALUE, name);
}
else if (kind == opt) {
rb_str_catf(str, "%"PRIsVALUE"=...", name);
}
else if (kind == keyreq) {
rb_str_catf(str, "%"PRIsVALUE":", name);
}
else if (kind == key) {
rb_str_catf(str, "%"PRIsVALUE": ...", name);
}
else if (kind == rest) {
if (name == ID2SYM('*')) {
forwarding = 1;
rb_str_cat_cstr(str, "...");
}
else {
rb_str_catf(str, "*%"PRIsVALUE, name);
}
}
else if (kind == keyrest) {
rb_str_catf(str, "**%"PRIsVALUE, name);
}
else if (kind == block) {
if (name == ID2SYM('&')) {
if (forwarding) {
rb_str_set_len(str, RSTRING_LEN(str) - 2);
}
else {
rb_str_cat_cstr(str, "...");
}
}
else {
rb_str_catf(str, "&%"PRIsVALUE, name);
}
}
else if (kind == nokey) {
rb_str_buf_cat2(str, "**nil");
}
if (i < RARRAY_LEN(params) - 1) {
rb_str_buf_cat2(str, ", ");
}
}
rb_str_buf_cat2(str, ")");
}
{ // source location
VALUE loc = rb_method_location(method);
if (!NIL_P(loc)) {
rb_str_catf(str, " %"PRIsVALUE":%"PRIsVALUE,
RARRAY_AREF(loc, 0), RARRAY_AREF(loc, 1));
}
}
rb_str_buf_cat2(str, ">");
return str;
} Возвращает понятное описание базового метода.
"cat".method(:count).inspect #=> "#<Method: String#count(*)>" (1..3).method(:map).inspect #=> "#<Method: Range(Enumerable)#map()>"
В последнем случае описание метода включает «владельца» исходного метода (Enumerable модуль, который включен в Range).
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#+(_)>""
static VALUE
method_name(VALUE obj)
{
struct METHOD *data;
TypedData_Get_Struct(obj, struct METHOD, &method_data_type, data);
return ID2SYM(data->me->called_id);
} Возвращает имя метода.
static VALUE
method_original_name(VALUE obj)
{
struct METHOD *data;
TypedData_Get_Struct(obj, struct METHOD, &method_data_type, data);
return ID2SYM(data->me->def->original_id);
} Возвращает исходное имя метода.
class C def foo; end alias bar foo end C.instance_method(:bar).original_name # => :foo
static VALUE
method_owner(VALUE obj)
{
struct METHOD *data;
TypedData_Get_Struct(obj, struct METHOD, &method_data_type, data);
return data->me->owner;
} Возвращает класс или модуль, определяющий метод. См. также Method#receiver.
(1..3).method(:map).owner #=> Enumerable
static VALUE
rb_method_parameters(VALUE method)
{
const rb_iseq_t *iseq = rb_method_iseq(method);
if (!iseq) {
return rb_unnamed_parameters(method_arity(method));
}
return rb_iseq_parameters(iseq, 0);
} Возвращает информацию о параметрах этого метода.
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]]
static VALUE
method_receiver(VALUE obj)
{
struct METHOD *data;
TypedData_Get_Struct(obj, struct METHOD, &method_data_type, data);
return data->recv;
} Возвращает связанный приемник объекта метода.
(1..3).method(:map).receiver # => 1..3
VALUE
rb_method_location(VALUE method)
{
return method_def_location(rb_method_def(method));
} Возвращает имя файла источника Ruby и номер строки, содержащие этот метод, или nil, если этот метод не был определен в Ruby (т. е. нативный).
static VALUE
method_super_method(VALUE method)
{
const struct METHOD *data;
VALUE super_class, iclass;
ID mid;
const rb_method_entry_t *me;
TypedData_Get_Struct(method, struct METHOD, &method_data_type, data);
iclass = data->iclass;
if (!iclass) return Qnil;
super_class = RCLASS_SUPER(RCLASS_ORIGIN(iclass));
mid = data->me->called_id;
if (!super_class) return Qnil;
me = (rb_method_entry_t *)rb_callable_method_entry_with_refinements(super_class, mid, &iclass);
if (!me) return Qnil;
return mnew_internal(me, me->owner, iclass, data->recv, mid, rb_obj_class(method), FALSE, FALSE);
} Возвращает Method суперкласса, который будет вызван при использовании super, или nil, если в суперклассе нет метода.
static VALUE
method_to_proc(VALUE method)
{
VALUE procval;
rb_proc_t *proc;
/*
* class Method
* def to_proc
* lambda{|*args|
* self.call(*args)
* }
* end
* end
*/
procval = rb_iterate(mlambda, 0, bmcall, method);
GetProcPtr(procval, proc);
proc->is_from_method = 1;
return procval;
} Возвращает объект Proc, соответствующий этому методу.
static VALUE
method_inspect(VALUE method)
{
struct METHOD *data;
VALUE str;
const char *sharp = "#";
VALUE mklass;
VALUE defined_class;
TypedData_Get_Struct(method, struct METHOD, &method_data_type, data);
str = rb_sprintf("#<% "PRIsVALUE": ", rb_obj_class(method));
mklass = data->klass;
if (RB_TYPE_P(mklass, T_ICLASS)) {
/* TODO: I'm not sure why mklass is T_ICLASS.
* UnboundMethod#bind() can set it as T_ICLASS at convert_umethod_to_method_components()
* but not sure it is needed.
*/
mklass = RBASIC_CLASS(mklass);
}
if (data->me->def->type == VM_METHOD_TYPE_ALIAS) {
defined_class = data->me->def->body.alias.original_me->owner;
}
else {
defined_class = method_entry_defined_class(data->me);
}
if (RB_TYPE_P(defined_class, T_ICLASS)) {
defined_class = RBASIC_CLASS(defined_class);
}
if (FL_TEST(mklass, FL_SINGLETON)) {
VALUE v = rb_ivar_get(mklass, attached);
if (data->recv == Qundef) {
rb_str_buf_append(str, rb_inspect(mklass));
}
else if (data->recv == v) {
rb_str_buf_append(str, rb_inspect(v));
sharp = ".";
}
else {
rb_str_buf_append(str, rb_inspect(data->recv));
rb_str_buf_cat2(str, "(");
rb_str_buf_append(str, rb_inspect(v));
rb_str_buf_cat2(str, ")");
sharp = ".";
}
}
else {
rb_str_buf_append(str, rb_inspect(mklass));
if (defined_class != mklass) {
rb_str_catf(str, "(% "PRIsVALUE")", defined_class);
}
}
rb_str_buf_cat2(str, sharp);
rb_str_append(str, rb_id2str(data->me->called_id));
if (data->me->called_id != data->me->def->original_id) {
rb_str_catf(str, "(%"PRIsVALUE")",
rb_id2str(data->me->def->original_id));
}
if (data->me->def->type == VM_METHOD_TYPE_NOTIMPLEMENTED) {
rb_str_buf_cat2(str, " (not-implemented)");
}
// parameter information
{
VALUE params = rb_method_parameters(method);
VALUE pair, name, kind;
const VALUE req = ID2SYM(rb_intern("req"));
const VALUE opt = ID2SYM(rb_intern("opt"));
const VALUE keyreq = ID2SYM(rb_intern("keyreq"));
const VALUE key = ID2SYM(rb_intern("key"));
const VALUE rest = ID2SYM(rb_intern("rest"));
const VALUE keyrest = ID2SYM(rb_intern("keyrest"));
const VALUE block = ID2SYM(rb_intern("block"));
const VALUE nokey = ID2SYM(rb_intern("nokey"));
int forwarding = 0;
rb_str_buf_cat2(str, "(");
for (int i = 0; i < RARRAY_LEN(params); i++) {
pair = RARRAY_AREF(params, i);
kind = RARRAY_AREF(pair, 0);
name = RARRAY_AREF(pair, 1);
// FIXME: in tests it turns out that kind, name = [:req] produces name to be false. Why?..
if (NIL_P(name) || name == Qfalse) {
// FIXME: can it be reduced to switch/case?
if (kind == req || kind == opt) {
name = rb_str_new2("_");
}
else if (kind == rest || kind == keyrest) {
name = rb_str_new2("");
}
else if (kind == block) {
name = rb_str_new2("block");
}
else if (kind == nokey) {
name = rb_str_new2("nil");
}
}
if (kind == req) {
rb_str_catf(str, "%"PRIsVALUE, name);
}
else if (kind == opt) {
rb_str_catf(str, "%"PRIsVALUE"=...", name);
}
else if (kind == keyreq) {
rb_str_catf(str, "%"PRIsVALUE":", name);
}
else if (kind == key) {
rb_str_catf(str, "%"PRIsVALUE": ...", name);
}
else if (kind == rest) {
if (name == ID2SYM('*')) {
forwarding = 1;
rb_str_cat_cstr(str, "...");
}
else {
rb_str_catf(str, "*%"PRIsVALUE, name);
}
}
else if (kind == keyrest) {
rb_str_catf(str, "**%"PRIsVALUE, name);
}
else if (kind == block) {
if (name == ID2SYM('&')) {
if (forwarding) {
rb_str_set_len(str, RSTRING_LEN(str) - 2);
}
else {
rb_str_cat_cstr(str, "...");
}
}
else {
rb_str_catf(str, "&%"PRIsVALUE, name);
}
}
else if (kind == nokey) {
rb_str_buf_cat2(str, "**nil");
}
if (i < RARRAY_LEN(params) - 1) {
rb_str_buf_cat2(str, ", ");
}
}
rb_str_buf_cat2(str, ")");
}
{ // source location
VALUE loc = rb_method_location(method);
if (!NIL_P(loc)) {
rb_str_catf(str, " %"PRIsVALUE":%"PRIsVALUE,
RARRAY_AREF(loc, 0), RARRAY_AREF(loc, 1));
}
}
rb_str_buf_cat2(str, ">");
return str;
} Возвращает удобочитаемое описание базового метода.
"cat".method(:count).inspect #=> "#<Method: String#count(*)>" (1..3).method(:map).inspect #=> "#<Method: Range(Enumerable)#map()>"
В последнем случае описание метода включает «владельца» исходного метода (Enumerable модуль, который включен в Range).
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#+(_)>""
static VALUE
method_unbind(VALUE obj)
{
VALUE method;
struct METHOD *orig, *data;
TypedData_Get_Struct(obj, struct METHOD, &method_data_type, orig);
method = TypedData_Make_Struct(rb_cUnboundMethod, struct METHOD,
&method_data_type, data);
RB_OBJ_WRITE(method, &data->recv, Qundef);
RB_OBJ_WRITE(method, &data->klass, orig->klass);
RB_OBJ_WRITE(method, &data->iclass, orig->iclass);
RB_OBJ_WRITE(method, &data->me, rb_method_entry_clone(orig->me));
return method;
} Отсоединяет meth от текущего получателя. Результирующий UnboundMethod впоследствии может быть привязан к новому объекту того же класса (см. UnboundMethod).
Ruby Core © 1993–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.