類別方法
方法
物件由 物件#方法
建立,並與特定物件關聯(不只與類別關聯)。它們可用於在物件內呼叫方法,並作為與反覆運算器關聯的區塊。它們也可以從一個物件解除關聯(建立 解除關聯的方法
),並與另一個物件關聯。
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)>]
公開實例方法
傳回一個程序,它是此方法與給定 g 的組合。傳回的程序會接收變數個引數,使用這些引數呼叫 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); }
如果兩個方法物件關聯到同一個物件,而且參照同一個方法定義,而且定義這些方法的類別是同一個類別或模組,則這兩個方法物件相等。
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; }
傳回一個程序,它是此方法與給定 g 的組合。傳回的程序會接收變數個引數,使用這些引數呼叫此方法,然後使用結果呼叫 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); }
傳回一個表示方法接受的引數數量的指示。對於接收固定數量的引數的方法,傳回非負整數。對於接收變數個引數的 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); }
使用指定的引數呼叫 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); }
傳回此方法的複製。
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; }
傳回根據方法的 curried proc。當 proc 以低於方法 arity 的參數數量呼叫時,會傳回另一個 curried proc。只有在提供足夠的參數以符合方法簽章時,才會實際呼叫方法。
在對具有變數參數的方法進行 currying 時,應提供選用的 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); }
如果兩個方法物件關聯到同一個物件,而且參照同一個方法定義,而且定義這些方法的類別是同一個類別或模組,則這兩個方法物件相等。
傳回對應於方法物件的雜湊值。
另請參閱 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); }
傳回基礎方法的人類可讀描述。
"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; }
傳回方法的名稱。
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); }
傳回方法的原始名稱。
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); }
傳回定義此方法的類別或模組。換句話說,
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; }
傳回此方法的參數資訊。
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)); }
傳回方法物件的繫結接收器。
(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; }
傳回包含此方法的 Ruby 來源檔名和行號,或在 Ruby 中未定義此方法時傳回 nil(即原生)。
VALUE rb_method_location(VALUE method) { return method_def_location(rb_method_def(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); }
傳回對應於此方法的 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; }
傳回基礎方法的人類可讀描述。
"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#+(_)>""
將 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; }