メソッド本体にrescue
とか書けたんだね。知らんかったよ。
成果物
情報源
メソッド
メソッドの呼出
まずレシーバ式を評価してレシーバとなるオブジェクトを得ます。レシーバ式が省略された場合は呼び出しを行っているブロックのself がレシーバです。
続いて引数式を左から右の順番で評価し、レシーバに対してメソッドの検索を行います。検索が失敗したら例外 NameError を発生、成功したらメソッドを実行します。
そういえばRubyってオーバーロードはできるのか? オーバーロードとは、同名であり異なる引数をもったメソッドを定義すること。
ググってみたらできないってさ。言語仕様的にはできないけど、ライブラリを使えばできると。
でもそのコードがダサい。
gem install overloader
require 'overloader' class A extend Overloader overload do def m; :m1; end def m(x); :m2; end def m(x, y); :m3; end end end
orverload
って3回も書かなきゃいけないrequire
extend
overload do
overload do
という文でネストされてしまう- ほかのメソッドと同じように書きたい
ふつうにやると単にメソッドが上書きされるだけでオーバーロードできない。
class A def m; :m1; end def m(x); :m2; end def m(x, y); :m3; end end
A.new.m #=>wrong number of arguments (given 0, expected 2) (ArgumentError)
ググったら3.0からできそうな雰囲気?
a.rb
class A def m; :m1; end def m(x); :m2; end def m(x, y); :m3; end end
typeprof a.rb > a.rbs
cat a.rbs
# TypeProf 0.12.0 # Classes class A def m: (untyped x) -> :m2 | (untyped x, untyped y) -> :m3 | -> :m1 end
このあとはsteep
というgemを使って静的型チェックする、とあった。
あれ、実行はできないの? 書いていないなので、できないっぽい。えー。じゃあオーバーロードできないってことかい。なんだよー先に言ってくれよ。
がっかり。
ドキュメントに戻ろう。
またメソッドを実行する際にはブロックを与えることが可能です。メソッドに対してブロックを与えると、そのメソッドの実行中にyield が実行されたときにはじめてブロックが評価されます。yield されなかったときはブロックは単に無視され、実行されません。
def m(&b); yield b; end m {p 'BLOCK'}
このコードって超わかりにくいわ。ブロックというクロージャと、yield
というイテレータとがつながらない。とくにほかのプログラミング言語をやっていると違和感ハンパない。
メソッドにブロックを与える場合、そのブロックはメソッドを呼び出す側のブロックの self と class を継承します。Module#module_eval/class_eval、 BasicObject#instance_eval の三つだけが例外で、以下のように変更されます。
メソッド | self |
class |
---|---|---|
Module#module_eval | そのレシーバ | そのレシーバ |
Module#class_eval | そのレシーバ | そのレシーバ |
BasicObject#instance_eval | そのレシーバ | そのレシーバの特異クラス |
はあ。よくわからんけども。
eval
eval の第二引数に Proc オブジェクトまたは Binding オブジェクトを与えたときは、その生成時のブロックのうえで文字列を評価します。
eval "p 'P'"
Procを渡すと怒られた。使い方知らんし。まあいいや。放置で。
eval "p 'P'" Proc.new
syntax error, unexpected constant, expecting end-of-input (SyntaxError)
メソッドの実行
メソッドの実行はフレームのうえにひとつだけブロックがある状態で開始します。このブロックを以下、仮にトップレベルブロックと呼んでおきます。トップレベルブロックの self はレシーバで、class は定義されていません。
フレームってなんぞ? よくわからんけども読み進める。
まず、省略不可能な引数が存在するなら、与えられた値をトップレベルブロックのローカル変数に代入します。
は? えーと、省略可能な引数って、キーワード引数のことかな? ローカル変数というのはキーワード引数の変数名のこと?
p = 'P' def m(p='') # ここで上のp='P'に''を代入するって言っているように見えるんですけど、そんなわけないよね? end
省略可能な引数が存在し、実際に省略されていたら、トップレベルブロック上でデフォルト式を評価し、その値をトップレベルブロックのローカル変数に代入します。実際には省略されなかったら、与えられた値を同じくローカル変数に代入します。
たぶんキーワード引数の説明だろう。
*args の形のパラメータ指定があるなら、残りの引数すべてを配列としてローカル変数に代入します。
可変長引数の説明だね。はいはい。
さらに、ブロック引数 blockvar が存在するならば、メソッドに与えられたブロックを Proc オブジェクト化してトップレベルブロック上のローカル変数 blockvar に代入します。ブロックが与えられていないなら nil を代入します。
ブロック引数の説明だね。はいはい。
続いて本体が評価され、メソッドレベルの rescue 節または else 節が評価され、最後に ensure 節が評価されます。
メソッドレベルのrescue
節ってどんなコード? まさか以下のように書けるの?
def m raise rescue => e p e ensure p '終了' end m
RuntimeError "終了" => RuntimeError
えええ! できたよ。マジか。知らんかった。ちゃんとコード例を出してくれや。
そういえばJavaはこういう書き方できたっけ? 忘れた。
ふつうは以下だと思うけど。これでも同じ結果になる。
def m begin raise rescue => e p e ensure p '終了' end end m
RuntimeError "終了" => RuntimeError
メソッド全体の値は return に渡した値です。 return が呼び出されなかった場合は、本体/rescue/else の中で最後に評価した式の値です。その三つともすべてがカラだった場合は nil になります。
つまりreturn
があったらたとえrescue
やelse
やensure
があっても戻り値はreturn
のやつを使うってことかな? 試してみよう。
def m return 'RETURN' raise rescue => e p e 'RESCUE' ensure p '終了' 'ENSURE' end m
"終了" => "RETURN"
ほーう。こうなるのか。つまりreturn
によってその時点でメソッド呼出元に戻される。なのでraise
されずrescue
されないと。でもensure
だけはその名のとおり実行を保障されると。でもensure
が最後に実行されたにもかかわらず、戻り値はreturn
で指定したものだと。これまでのルールでは、最後に実行した式の戻り値が返るはず。それだと'ENSURE'
が返るはず。でもreturn
で指定した'RETURN'
が返った。return
指定すると戻り値の値を指定するという動作になるってことか。
return
しなかったら?
def m 'RETURN' raise rescue => e p e 'RESCUE' ensure p '終了' 'ENSURE' end m
"終了" => "RESCUE"
え、RESCUE
なの? てっきりENSURE
かと思っていたんだが。これは予想外。
つまりensure
は必ず実行されることを保証する。けれど戻り値については「最後に実行した式の戻り値を返す」というルールに当てはまらない。ということか。
これは難しい。ちょっとまとめよう。
戻り値は次のうち最初にみつかったものに決定される。
return
指定された値- 最後に実行した式の戻り値
- ただし
ensure
は対象外
- ただし
その三つともすべてがカラだった場合は nil になります。
といっているが、メソッドの戻り値がnil
になることなんてあるの? だってメソッド定義式のところで以下のように言っているよ?
メソッド定義式は、メソッド名を Symbol にしたオブジェクトを返します。
ためしてみる。あれ、nil
が返ってきたぞ?
def m; end m #=> nil
あー、もしかしてこういうこと?
a = def m; end a #=> :m
やっぱり。はいはい。def
式自体の戻り値がシンボルだって話だったのね。
ブロック付きメソッド呼び出し
メソッドに対してブロックが与えられていたらそのメソッドをブロック付きメソッドと呼びます。ブロック付きメソッドからは yield によって与えられたブロックに実行を移すことができます。
def m(&block) yield block end m {p 'BLOCK'}
"BLOCK" => "BLOCK"
m
はブロック付きメソッドである。
というか、ブロック引数&block
がなくても動作してしまったんだが?
def m yield end m {p 'BLOCK'}
"BLOCK" => "BLOCK"
そういうもんなの?
どういうときに使うんだろう。処理を一部だけブロックのものにしたいとき? でもメソッド本体側ではyield
があるからブロックありきなんだよね。
もしブロックが渡されなかったらどうなる?
def m yield end m
no block given (yield) (LocalJumpError)
怒られたよ。ふーん。
ブロック引数があるのとないのとではどう使い分けるんだ? メソッド本体で複数の異なるブロックのうち条件分岐で使い分けたいときにはブロック引数をつける、とか? あるいは単純に、複数のブロックを受け取りたいときとか。
ブロック引数をとることができます。
先述のとおり。&block
のことだと思われる。
break … ブロックがスタックフレーム上にあるなら、そのフレームのブロックの直後にジャンプします。ブロック付きメソッドを break して終了したらその値は nil です。スタックフレーム上にないなら例外 LocalJumpError を発生します。
C言語のbreak
と同じだと思う。
next ブロックの終わりまでジャンプ
next
はC言語でいうcontinue
と同じっぽい。
retry 複雑だ…
は? え、なにその感想。ちょっとまって。確認させて。retry
というキーワードがあるってこと? すでにそこからわからんのですが。
def m(times: 3) try = 0 begin try += 1 yield rescue retry if try < times raise end end m { p '実行' }
"実行" => "実行"
あれ、1回しか実行されてないやん。どゆこと? てっきり3回実行
と表示されるかと思ったのに。
わからん。
eval, instance_eval, module_eval
これなんだっけ
知らんがな。こっちが聞きたいわ。
ようするに、その文脈でeval
するってことね。
対象環境
- Raspbierry pi 4 Model B
- Raspberry Pi OS buster 10.0 2020-08-20 ※
- bash 5.0.3(1)-release
- Ruby 3.0.2