ruby は メソッドに return が不要なのではなく、元々そうなっているだけ
皆さん、こんにちは。MUGENUP の osada です。 いきなりですが、問題です。
def if_expression(flag) if flag "NG" else "OK" end end
Ruby は 最後に評価された値が返る
と言われていますね。
では、上記のメソッドで
if_expression false
の返り値は、何ですか?
はい、正解です。OK
が返ってきますね。
ちょっと長いので、リファクタリングしましょう。
def if_modifier(flag) "OK" "NG" if flag end
さてもう一度。
if_modifier(false)
の返り値は、何ですか?
Ruby は 最後に評価された値が返る
と言われていましたよね?
はい。正解です。nil
が返ってきますね。
え、"OK"じゃないのかって?
いいえ、後置if は if とは全く違います
。
右辺の条件が成立する時に、左辺の式を評価してその結果を返します。 条件が成立しなければ nil を返します。 (http://docs.ruby-lang.org/ja/2.1.0/doc/spec=2fcontrol.html)
という流れでreturn を書けばこんな混乱しないのに
という話がでました。
いやいや、return
とか不要じゃない?という立場でこの記事を書いています。
ruby は 最後に評価される値
が返る、と言われますが、それはどのような意味なのでしょうか?
そんな本日は ruby の挙動についてのお話です。 ターゲットは、アセンブラに興味がある人です(アセンブラを使いこなす人は、対象外でお願いします (笑))
if と、if 修飾子 (後置 if) の違い
まず if と if 修飾子を使ったコードを書きます。
# return_if.rb def if_expression(flag) if flag "NG" else "OK" end end def if_modifier(flag) "OK" "NG" if flag end
そしておもむろに、ディスアセンブラを書きます(!)。
# disasm.rb iseq = RubyVM::InstructionSequence.compile_file ARGV.first, false print iseq.disasm
さきほどのファイルを食わせます
[master]~/projects/ruby_test/practice/if_expression: ruby disasem.rb return_if.rb == disasm: <RubyVM::InstructionSequence:<main>@return_if.rb>============ 0000 putspecialobject 1 ( 20) 0002 putspecialobject 2 0004 putobject :if_expression 0006 putiseq if_expression 0008 send <callinfo!mid:core#define_method, argc:3, ARGS_SKIP> 0010 pop 0011 putspecialobject 1 ( 28) 0013 putspecialobject 2 0015 putobject :if_modifier 0017 putiseq if_modifier 0019 send <callinfo!mid:core#define_method, argc:3, ARGS_SKIP> 0021 leave == disasm: <RubyVM::InstructionSequence:if_expression@return_if.rb>===== local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s1) [ 2] flag<Arg> 0000 getlocal flag, 0 ( 21) 0003 branchunless 11 0005 jump 7 0007 putstring "NG" ( 22) 0009 jump 13 ( 21) 0011 putstring "OK" ( 24) 0013 leave == disasm: <RubyVM::InstructionSequence:if_modifier@return_if.rb>======= local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s1) [ 2] flag<Arg> 0000 getlocal flag, 0 ( 30) 0003 branchunless 11 0005 jump 7 0007 putstring "NG" 0009 jump 12 0011 putnil 0012 leave
初めの ブロックはメソッド定義なので飛ばしまずが、 なんとなく気分はわかっていただけると思います。
2番目の if_expresson
ブロックと、
3番目の if_modifier
ブロックを見ていくことにしましょう。
if_expression
2番めのブロックは、if_expression のコードです。
== disasm: <RubyVM::InstructionSequence:if_expression@return_if.rb>===== local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s1) [ 2] flag<Arg> 0000 getlocal flag, 0 ( 21) 0003 branchunless 11 0005 jump 7 0007 putstring "NG" ( 22) 0009 jump 13 ( 21) 0011 putstring "OK" ( 24) 0013 leave
- 0番で
flag
変数から値をとりだします。 - 3番で、それが
nil, false
でなければ、11番に飛びます。
if
なのに、branchunless
というのは面白いですね。
これは、否定を確認した方が速いからでしょう。
flag
が false
のとき、
- 11番は、"OK"を スタックに積みます。
- 13番で
leave
します。
flag
が true
のとき、
- 5番で、
jump 7
します。 - 7番で "NG" を スタックに積みます。
- 9番で
jump 13
します。 - 13番で
leave
します。
if_modifier
3番めのブロックは、if_modifier のコードです。
== disasm: <RubyVM::InstructionSequence:if_modifier@return_if.rb>======= local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s1) [ 2] flag<Arg> 0000 getlocal flag, 0 ( 30) 0003 branchunless 11 0005 jump 7 0007 putstring "NG" 0009 jump 12 0011 putnil 0012 leave
- 0番で
flag
変数から値をとりだします。 - 3番で、それが
nil, false
でなければ、11番に飛びます。 - 11番で
putnil
でnil
をスタックに積みます - 12番で
lreave
します。
はい、成立しないときは、nil が返りますね。仕様ですから仕方ないですよね。
あれ? "OK" は、どこに行ったのでしょう?
putstring
すらないとか、かわいそうですね。
return が不要な理由
ruby に return が不要な理由は、もうお分かりですね。
というかそもそも、return
ではなくleave
ですね。
ruby としては、メソッドの元から去る(leave
)だけで、
値を返し(return
)ているつもりはなさそうです。
return
は単に、メソッドから去るタイミングを指定しているだけなので、
最後に書く必要はない、ということなのでしょう。
あれ?そんなのどの言語も一緒じゃん! と思われません?
ではここで、LL界の委員長 python
にご出座いただきましょう。
python は return が必須です!
pythonは return が必須です。
def if_expression(flag): if flag: return "NG" else: return "OK" import dis dis.dis(if_expression)
[master]~/projects/python_test: python if_expression.py 2 0 LOAD_FAST 0 (flag) 3 POP_JUMP_IF_FALSE 10 3 6 LOAD_CONST 1 ('NG') 9 RETURN_VALUE 5 >> 10 LOAD_CONST 2 ('OK') 13 RETURN_VALUE 14 LOAD_CONST 0 (None) 17 RETURN_VALUE
左端はソース行なので、無視してください。
- 0番で
flag
から ロードします - 3番で、それが FALSE なら、 10番にジャンプします
これも、false
をチェックするのはお決まりですね。
flag
が true
のとき、
- 6番で "NG" をスタックに積みます
- 9番で、
RETURN_VALUE
します(return
だよ!やったね!)
flag
が true
のとき、
- 10番で、"OK" をスタックに積みます。
- 13番で、
RETURN_VALUE
します
あれ?2行残っていますね。
- 14番で、
None
を スタックに積みます。 - 17番で、
RETURN_VALUE
します
これは、python
の関数が、return がないときはNoneを返す
という仕様になっているからです。
python
はインデントがブロックを表す
ので、
この関数自体の返り値が書かれていません。
よって、最後に None
を返すコードが暗黙的に追加されています。
さて、動作はruby
とほとんど同じですね。
では、return
を書かないとどうなるか見てみましょう。
def if_expression(flag): if flag: "OK" else: "NG" import dis dis.dis(if_expression)
[master]~/projects/python_test: python if_expression.py 17 0 LOAD_FAST 0 (flag) 3 POP_JUMP_IF_FALSE 9 18 6 JUMP_FORWARD 0 (to 9) 20 >> 9 LOAD_CONST 0 (None) 12 RETURN_VALUE
何もない……。
スタックに積んだ物が返る、というのはどの言語でも一緒ですが、
return
をどのように取り扱うかで、コードが異なる、ということですね。
まとめ
if 修飾子
は 成立しないときnil
を返します- メソッドは最後にスタックに積まれた値を返します
RubyVM::InstructionSequence
で VM のコードが読めますif
はbranchunless
を判定しますruby
のreturn
は、メソッドの返りのタイミングの指定です。python
のreturn
は、関数の返り値の指定です。
ということで、ruby
にとって、return
は メソッドの返り値の指定ではなく、
どのタイミングでメソッドからleave
するかの指定、という意味なので、
実質return
が不要になる、ということのようです。
さて、ruby
に return
必要ですか ?
メソッドの値を返す場合は、必ずreturnを使用する。
それと return なしのほうがちょっとだけ効率もいい。 それはなぜかというと RubyHackingGuide でも見てくれればわかるのだが、 return は結局 longjmp だからだ。
LoveRubyNet Wiki: RubyCodingStyle
Avoid return where not required.
styleguide/RUBY-STYLE at master · chneukirchen/styleguide
……あれ〜??
(おまけ) 人生、宇宙、すべての答え
ruby の ソースを追いかけていて、人生の答えを知りました。 さすが ruby ですね。
/** @c joke @e The Answer to Life, the Universe, and Everything @j 人生、宇宙、すべての答え。 */ DEFINE_INSN answer () () (VALUE ret) { ret = INT2FIX(42); }
http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/insns.def?view=markup