类 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)>]

公共实例方法

meth << g → a_proc 点击切换源代码

返回一个 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
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);
}
meth == other_meth → true 或 false 点击切换源代码

如果两个方法对象绑定到同一个对象并引用同一个方法定义,并且定义方法的类是同一个类或模块,则它们相等。

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 *)RTYPEDDATA_GET_DATA(method);
    m2 = (struct METHOD *)RTYPEDDATA_GET_DATA(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;
}
也称为:eql?
===(*args)

用指定参数调用 meth,返回方法的返回值。

m = 12.method("+")
m.call(3)    #=> 15
m.call(20)   #=> 32
别名为:call
meth >> g → a_proc 点击切换源代码

返回一个 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_compose_to_right(VALUE self, VALUE g)
{
    g = to_callable(g);
    self = method_to_proc(self);
    return proc_compose_to_right(self, g);
}
[](*args)

用指定参数调用 meth,返回方法的返回值。

m = 12.method("+")
m.call(3)    #=> 15
m.call(20)   #=> 32
别名为:call
arity → integer 点击切换源代码

返回方法接受的参数数量的指示。对于接受固定数量参数的方法,返回一个非负整数。对于接受可变数量参数的 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
method_arity_m(VALUE method)
{
    int n = method_arity(method);
    return INT2FIX(n);
}
call(args, ...) → obj 点击切换源代码

用指定参数调用 meth,返回方法的返回值。

m = 12.method("+")
m.call(3)    #=> 15
m.call(20)   #=> 32
static VALUE
rb_method_call_pass_called_kw(int argc, const VALUE *argv, VALUE method)
{
    return rb_method_call_kw(argc, argv, method, RB_PASS_CALLED_KEYWORDS);
}
别名:===, []
clone → new_method 点击切换源代码

返回此方法的克隆。

class A
  def foo
    return "bar"
  end
end

m = A.new.method(:foo)
m.call # => "bar"
n = m.clone.call # => "bar"
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->owner, orig->owner);
    RB_OBJ_WRITE(clone, &data->me, rb_method_entry_clone(orig->me));
    return clone;
}
curry → proc 点击切换源代码
curry(arity) → proc

返回基于方法的柯里化 proc。当 proc 被调用时,如果参数数量小于方法的 arity,则返回另一个柯里化 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
rb_method_curry(int argc, const VALUE *argv, VALUE self)
{
    VALUE proc = method_to_proc(self);
    return proc_curry(argc, argv, proc);
}
eql?(other_meth) → true 或 false

如果两个方法对象绑定到同一个对象并引用同一个方法定义,并且定义方法的类是同一个类或模块,则它们相等。

别名:==
hash → integer 点击切换源代码

返回与方法对象相对应的哈希值。

另请参阅 Object#hash.

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);
}
inspect → string 点击切换源代码

返回底层方法的人类可读描述。

"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_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->iclass;
    if (!mklass) 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 (data->recv == Qundef) {
        // UnboundMethod
        rb_str_buf_append(str, rb_inspect(defined_class));
    }
    else if (FL_TEST(mklass, FL_SINGLETON)) {
        VALUE v = RCLASS_ATTACHED_OBJECT(mklass);

        if (UNDEF_P(data->recv)) {
            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 {
        mklass = data->klass;
        if (FL_TEST(mklass, FL_SINGLETON)) {
            VALUE v = RCLASS_ATTACHED_OBJECT(mklass);
            if (!(RB_TYPE_P(v, T_CLASS) || RB_TYPE_P(v, T_MODULE))) {
                do {
                   mklass = RCLASS_SUPER(mklass);
                } while (RB_TYPE_P(mklass, T_ICLASS));
            }
        }
        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, "(");

        if (RARRAY_LEN(params) == 3 &&
            RARRAY_AREF(RARRAY_AREF(params, 0), 0) == rest &&
            RARRAY_AREF(RARRAY_AREF(params, 0), 1) == ID2SYM('*') &&
            RARRAY_AREF(RARRAY_AREF(params, 1), 0) == keyrest &&
            RARRAY_AREF(RARRAY_AREF(params, 1), 1) == ID2SYM(idPow) &&
            RARRAY_AREF(RARRAY_AREF(params, 2), 0) == block &&
            RARRAY_AREF(RARRAY_AREF(params, 2), 1) == ID2SYM('&')) {
            forwarding = 1;
        }

        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('*')) {
                    rb_str_cat_cstr(str, forwarding ? "..." : "*");
                }
                else {
                    rb_str_catf(str, "*%"PRIsVALUE, name);
                }
            }
            else if (kind == keyrest) {
                if (name != ID2SYM(idPow)) {
                    rb_str_catf(str, "**%"PRIsVALUE, name);
                }
                else if (i > 0) {
                    rb_str_set_len(str, RSTRING_LEN(str) - 2);
                }
                else {
                    rb_str_cat_cstr(str, "**");
                }
            }
            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;
}
别名:to_s
name → symbol 点击切换源代码

返回方法的名称。

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);
}
original_name → symbol 点击切换源代码

返回方法的原始名称。

class C
  def foo; end
  alias bar foo
end
C.instance_method(:bar).original_name # => :foo
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);
}
owner → class_or_module 点击切换源代码

返回定义此方法的类或模块。换句话说,

meth.owner.instance_methods(false).include?(meth.name) # => true

只要方法没有被删除/未定义/替换,就成立(如果方法是私有的,则使用 private_instance_methods 而不是 instance_methods)。

另请参阅 Method#receiver

(1..3).method(:map).owner #=> Enumerable
static VALUE
method_owner(VALUE obj)
{
    struct METHOD *data;
    TypedData_Get_Struct(obj, struct METHOD, &method_data_type, data);
    return data->owner;
}
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]]
static VALUE
rb_method_parameters(VALUE method)
{
    return method_def_parameters(rb_method_def(method));
}
receiver → object 点击切换源代码

返回方法对象的绑定接收者。

(1..3).method(:map).receiver # => 1..3
static VALUE
method_receiver(VALUE obj)
{
    struct METHOD *data;

    TypedData_Get_Struct(obj, struct METHOD, &method_data_type, data);
    return data->recv;
}
source_location → [String, Integer] 点击切换源代码

返回包含此方法的 Ruby 源文件名和行号,如果此方法不是在 Ruby 中定义的(即原生),则返回 nil。

VALUE
rb_method_location(VALUE method)
{
    return method_def_location(rb_method_def(method));
}
super_method → method 点击切换源代码

返回超类的 Method,当使用 super 时将调用该方法,如果超类中没有方法,则返回 nil。

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;
    if (data->me->def->type == VM_METHOD_TYPE_ALIAS && data->me->defined_class) {
        super_class = RCLASS_SUPER(rb_find_defined_class_by_owner(data->me->defined_class,
            data->me->def->body.alias.original_me->owner));
        mid = data->me->def->body.alias.original_me->def->original_id;
    }
    else {
        super_class = RCLASS_SUPER(RCLASS_ORIGIN(iclass));
        mid = data->me->def->original_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);
}
to_proc → proc 点击切换源代码

返回与该方法对应的 Proc 对象。

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_block_call(rb_mRubyVMFrozenCore, idLambda, 0, 0, bmcall, method);
    GetProcPtr(procval, proc);
    proc->is_from_method = 1;
    return procval;
}
to_s → string

返回底层方法的人类可读描述。

"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#+(_)>""
别名:inspect
unbind → unbound_method 点击切换源代码

meth与其当前接收者分离。生成的 UnboundMethod 随后可以绑定到同一类的新的对象(参见 UnboundMethod)。

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, Qundef);
    RB_OBJ_WRITE(method, &data->iclass, orig->iclass);
    RB_OBJ_WRITE(method, &data->owner, orig->me->owner);
    RB_OBJ_WRITE(method, &data->me, rb_method_entry_clone(orig->me));

    return method;
}