== と ===の違いについて
今回はRubyにおける==と===の違いについて調べました。 ===メソッドはwhen case文の中で内部的に使われているので、違いをわかっていないと思わぬところではまります。
先に結論から言っておくと
ということです。常に違う動きをするわけではないというのが、初めわかってなかったんですが、
これを理解するとスッと理解できました。
===メソッドが拡張されていないクラス
まずは===メソッドが拡張されていないクラスについて考えます。これは上記の通り==メソッドと同じ動きをするので、 例えば拡張されていないStringクラスでは
"hoge" == "hoge" => true "hoge" === "hoge" => true
といったように==と===で同じ挙動を示します。
=== メソッドが拡張されているクラス
===メソッドが拡張されているクラスには以下の4つがあります。
- Range
- Regexp
- Proc
- Module, Class
1つずつどのような挙動の違いがあるのか確認していきましょう。
Range
Rangeの===は引数が自身の範囲内に含まれるなら真を返します。
(1..5) == 3 #これは同値性を判定しているのでfalse => false (1..5) === 3 #拡張された===が呼び出される => true
ここで注意しないといけないのは
3 === (1..5) => false
左右を入れ替えるとfalseになるということです。これは3(Numeric)の===を呼び出しているため、拡張されていない===メソッドが呼び出されて同値性を判定した結果falseになるということです。このように===拡張されたクラスを扱う時は左右を入れ替えるだけで真偽値が変わってしまうので注意が必要です。
ちなみにRangeの実装は以下のようになっています(注: C言語)
/* * call-seq: * rng === obj -> true or false * * Returns <code>true</code> if +obj+ is an element of the range, * <code>false</code> otherwise. Conveniently, <code>===</code> is the * comparison operator used by <code>case</code> statements. * * case 79 * when 1..50 then print "low\n" * when 51..75 then print "medium\n" * when 76..100 then print "high\n" * end * * <em>produces:</em> * * high */ static VALUE range_eqq(VALUE range, VALUE val) { return rb_funcall(range, rb_intern("include?"), 1, val); }
Regexp
Regexpは引数の文字列がマッチした場合は真を返します。
/k/ == 'kazuya' => false /k/ === 'kazuya' => true
実装は以下のようになっています。(cは辛いですね。。。)
/* * call-seq: * rxp === str -> true or false * * Case Equality---Used in case statements. * * a = "HELLO" * case a * when /^[a-z]*$/; print "Lower case\n" * when /^[A-Z]*$/; print "Upper case\n" * else; print "Mixed case\n" * end * #=> "Upper case" * * Following a regular expression literal with the #=== operator allows you to * compare against a String. * * /^[a-z]*$/ === "HELLO" #=> false * /^[A-Z]*$/ === "HELLO" #=> true */ VALUE rb_reg_eqq(VALUE re, VALUE str) { long start; str = reg_operand(str, FALSE); if (NIL_P(str)) { rb_backref_set(Qnil); return Qfalse; } start = rb_reg_search(re, str, 0, 0); if (start < 0) { return Qfalse; } return Qtrue; }
Proc
Procついては一番トリッキーです。なんとblockの呼び出しができます。 Proc#===(arg) -> () argにブロックの引数を入れることができます。
proc = Proc.new{|a, b| puts a; puts b} proc.call(1,2) 1 2 => nil proc === [1, 2] 1 2 => nil
クエリ:Proc#=== | るりまサーチ なんでこんな実装になっているかというと、冒頭でも書きましたがwhen case文で使うためですね。 こんな感じでProcそのものの同値判定ではなくProcの戻り値で判定を行うことができます
proc = Proc.new{"hello"} greeting = "hello" case greeting when proc #ここでproc===が呼ばれる puts "this is hello" end => "this is hello"
こちらも実装を確認してみましょう。 case文で使うための実装ですよということが書いてあります。
/* Document-method: === * * call-seq: * proc === obj -> result_of_proc * * Invokes the block with +obj+ as the proc's parameter like Proc#call. It * is to allow a proc object to be a target of +when+ clause in a case * statement. */ /* CHECKME: are the argument checking semantics correct? */ /* * call-seq: * prc.call(params,...) -> obj * prc[params,...] -> obj * prc.(params,...) -> obj * * Invokes the block, setting the block's parameters to the values in * <i>params</i> using something close to method calling semantics. * Generates a warning if multiple values are passed to a proc that * expects just one (previously this silently converted the parameters * to an array). Note that <code>prc.()</code> invokes * <code>prc.call()</code> with the parameters given. It's a syntax sugar to * hide "call". * * Returns the value of the last expression evaluated in the block. See * also Proc#yield. * * a_proc = Proc.new { |scalar, *values| values.collect { |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] * * For procs created using <code>lambda</code> or <code>->()</code> an error * is generated if the wrong number of parameters are passed to a Proc with * multiple parameters. For procs created using <code>Proc.new</code> or * <code>Kernel.proc</code>, extra parameters are silently discarded. * * a_proc = lambda {|a,b| a} * a_proc.call(1,2,3) * * <em>produces:</em> * * prog.rb:4:in `block in <main>': wrong number of arguments (given 3, expected 2) (ArgumentError) * from prog.rb:5:in `call' * from prog.rb:5:in `<main>' * */ #if 0 static VALUE proc_call(int argc, VALUE *argv, VALUE procval) { VALUE vret; const rb_block_t *blockptr = 0; const rb_iseq_t *iseq; rb_proc_t *proc; VALUE passed_procval; GetProcPtr(procval, proc); iseq = proc->block.iseq; if (RUBY_VM_IFUNC_P(iseq) || iseq->body->param.flags.has_block) { if (rb_block_given_p()) { rb_proc_t *passed_proc; passed_procval = rb_block_proc(); GetProcPtr(passed_procval, passed_proc); blockptr = &passed_proc->block; } } vret = rb_vm_invoke_proc(GET_THREAD(), proc, argc, argv, blockptr); RB_GC_GUARD(procval); RB_GC_GUARD(passed_procval); return vret; } #endif #if SIZEOF_LONG > SIZEOF_INT static inline int check_argc(long argc) { if (argc > INT_MAX || argc < 0) { rb_raise(rb_eArgError, "too many arguments (%lu)", (unsigned long)argc); } return (int)argc; } #else #define check_argc(argc) (argc) #endif VALUE rb_proc_call(VALUE self, VALUE args) { VALUE vret; rb_proc_t *proc; GetProcPtr(self, proc); vret = rb_vm_invoke_proc(GET_THREAD(), proc, check_argc(RARRAY_LEN(args)), RARRAY_CONST_PTR(args), 0); RB_GC_GUARD(self); RB_GC_GUARD(args); return vret; } VALUE rb_proc_call_with_block(VALUE self, int argc, const VALUE *argv, VALUE pass_procval) { VALUE vret; rb_proc_t *proc; rb_block_t *block = 0; GetProcPtr(self, proc); if (!NIL_P(pass_procval)) { rb_proc_t *pass_proc; GetProcPtr(pass_procval, pass_proc); block = &pass_proc->block; } vret = rb_vm_invoke_proc(GET_THREAD(), proc, argc, argv, block); RB_GC_GUARD(self); RB_GC_GUARD(pass_procval); return vret; }
Module, Class
Module, Classは引数が自身または自身のサブクラスのインスタンスであれば真を返します
"hello" === String => false String === "hello" => true
こちらも実装を確認しておきます
/* * call-seq: * mod === obj -> true or false * * Case Equality---Returns <code>true</code> if <i>obj</i> is an * instance of <i>mod</i> or and instance of one of <i>mod</i>'s descendants. * Of limited use for modules, but can be used in <code>case</code> statements * to classify objects by class. */ static VALUE rb_mod_eqq(VALUE mod, VALUE arg) { return rb_obj_is_kind_of(arg, mod); } ... VALUE rb_obj_is_kind_of(VALUE obj, VALUE c) { VALUE cl = CLASS_OF(obj); c = class_or_module_required(c); return class_search_ancestor(cl, RCLASS_ORIGIN(c)) ? Qtrue : Qfalse; }
って感じです。when caseの内部でしか基本使われないとはいえ理解しておいて損はないかと思います!