「クラス/メソッドの定義」を読む2
メソット内にメソッドを定義できる。新しい知識はそのくらいかな?
成果物
情報源
演算子式の定義
演算子式において、「再定義可能な演算子」に分類された演算子の実装はメソッドなので、定義することが可能です。
これらの演算子式を定義する例を以下に挙げます。
# 二項演算子 def +(other) # obj + other def -(other) # obj - other # 単項プラス/マイナス def +@ # +obj def -@ # -obj # 要素代入 def foo=(value) # obj.foo = value # [] と []= def [](key) # obj[key] def []=(key, value) # obj[key] = value def []=(key, key2, value) # obj[key, key2] = value # バッククォート記法 def `(arg) # `arg` または %x(arg)
バッククォート記法の実装はメソッドなのでこのように再定義が可能です。普通はこのメソッドを再定義するべきではありませんが、まれにOS(シェル)のコマンド実行の挙動に不具合がある場合などに利用できます。
演算子のオーバーライドはあんまり使わないと思う。使いやすいライブラリを作るときくらいかな?
メソッド定義のネスト
ネスト可能です。ネストされた定義式は、それを定義したメソッドが実行された時に定義されます。このことを除けば、普通のメソッド定義式と同じです。以下の例を参照してください。
class Foo def foo def bar p :bar end end def Foo.method_added(name) puts "method \"#{name}\" was added" end end obj = Foo.new obj.bar rescue nil # => undefined method `bar' for #<Foo:0x4019eda4> obj.foo # => method "bar" was added obj.foo # => warning: method redefined; discarding old bar Foo.new.bar # => :bar (他のインスタンスでも定義済み)
名前重複を避けやすくなるので便利なときもありそう。
メソッドの評価
メソッドが呼び出されると、以下の順で式が評価されます。
- 指定されていれば引数のデフォルト式
- メソッドの本体 body
- 指定されていれば例外の発生の有無によりメソッド定義式の rescue 節または else 節
- 指定されていれば ensure 節
- 引数のデフォルト式も含め、すべてそのメソッドのコンテキストで評価されます。
はい。
メソッドの戻り値は return に渡した値です。return が呼び出されなかった場合は、 body の最後の式の値を返します。 body の最後の式が値を返さない式の場合は nil を返します。
はい。
またメソッドは定義する前に呼び出すことはできません。例えば
foo # <- foo は未定義 def foo print "foo\n" end
は未定義メソッドの呼び出しで例外 NameError を発生させます。
はい。わかりきったことばかり。
特異メソッド定義
例:
def foo.test print "this is foo\n" end
文法:
def 式 `.' 識別子 [`(' [引数 [`=' default]] ... [`,' `*' 引数 ]`)'] 式.. [rescue [error_type,..] [=> evar] [then] 式..].. [else 式..] [ensure 式..] end
特異メソッドとはクラスではなくある特定のオブジェクトに固有のメソッドです。特異メソッドの定義はネストできます。
ネストしてどうするの? どういうときにネストしたくなるの? 想像できない。
クラスの特異メソッドはそのサブクラスにも継承されます。言い替えればクラスの特異メソッドは他のオブジェクト指向システムにおけるクラスメソッドの働きをすることになります。
なぜ継承されることとクラスメソッドをつなげるのか。ぜんぜんちがくない?
特異メソッド定義式は、メソッド名を Symbol にしたオブジェクトを返します。
はい。
ancestors
による継承リストの説明は一切なし。これじゃ特異クラスや特異メソッドが何者かよくわからない。オブジェクト固有メソッドなのにクラスメソッドだとも言われているから「は?」と思う。相変わらずよくわからん。
クラスメソッドの定義
Ruby におけるクラスメソッドとはクラスの特異メソッドのことです。Ruby では、クラスもオブジェクトなので、普通のオブジェクトと同様に特異メソッドを定義できます。
やっと明言してくれた。Rubyにはクラスメソッドなんてない。ほかのプログラミング言語でいうところのクラスメソッドに相当するものは特異メソッドである。
したがって、何らかの方法でクラスオブジェクトにメソッドを定義すれば、それがクラスメソッドとなります。具体的には以下のようにして定義することが出来ます(モジュールも同様です)。
# 特異メソッド方式。 class Hoge def Hoge.foo end end # クラス定義の外でも良い def Hoge.bar end # 以下のようにすればクラス名が変わってもメソッド部の変更が不要 class Hoge def self.baz 'To infinity and beyond!' end end # 特異クラス方式。複数のメソッドを一度に定義するとき向き class << Hoge def bar 'bar' end end # モジュールをクラスに extend すれば、モジュールのインスタンス # メソッドがクラスメソッドになる module Foo def foo end end class Hoge extend Foo end
extend については、Object#extend を参照してください。
クラスメソッドを定義する方法が多すぎる。
ところでクラスメソッドはクラスオブジェクトがもつ特異メソッド、ということだよね? じゃあインスタンスオブジェクトがもつ特異メソッドはなんて呼ぶの? そっちの呼び分けする名前がないから混乱するんだよね。だってクラスオブジェクトもインスタンスオブジェクトも両方オブジェクトでしょ? で、オブジェクト固有のメソッドのことを特異メソッドと呼ぶ。どっちも特異メソッドと呼ぶから見分けがつかない。でもクラスオブジェクト固有メソッドのほうだけはほかのプログラミング言語にもある「クラスメソッド」という呼び方をしている。だったらほかのプログラミング言語にはないインスタンスオブジェクト固有のメソッドにも名前を付けてほしい。
呼び方 | 意味 |
---|---|
クラスメソッド(特異メソッド) | クラスオブジェクト固有メソッド |
?(特異メソッド) | インスタンスオブジェクト固有メソッド |
インスタンスメソッドはダメ。すでにある。だれか短くて呼びやすい名前つけてよ。
呼び出し制限
メソッドは public、private、protected の三通りの呼び出し制限を持ちます。
アクセス修飾子のことね。
- public に設定されたメソッドは制限なしに呼び出せます。
- private に設定されたメソッドは関数形式でしか呼び出せません。 ただし self.foo のように self. と書かれている場合は呼び出すことができます。
- protected に設定されたメソッドは、そのメソッドを持つオブジェクトが selfであるコンテキスト(メソッド定義式やinstance_eval)でのみ呼び出せ ます。
例: protected の可視性
class Foo def foo p caller.last end protected :foo end obj = Foo.new # そのままでは呼べない obj.foo rescue nil # => -:11 - protected method `foo' called for #<Foo:0x401a1860> (NameError) # クラス定義内でも呼べない class Foo Foo.new.foo rescue nil # => -:15 - protected method `foo' called for #<Foo:0x4019eea8> # メソッド定義式内で呼べる def bar self.foo end end Foo.new.bar # => ["-:21"] # 特異メソッド定義式内でも呼べる def obj.bar self.foo rescue nil end obj.bar # => ["-:27"]
protected
は特異メソッド定義式内でも呼べるようだ。特異メソッドって継承とは違う気がするけど。まあとにかくそういうものとして覚えるしかない。
デフォルトでは def 式がクラス定義の外(トップレベル)にあれば private、クラス定義の中にあれば public に定義します。これは Module#public、Module#private、 Module#protected を用いて変更できます。ただし Object#initialize という名前のメソッドと Object#initialize_copy という名前のメソッドは定義する場所に関係なく常に private になります。
例:
def foo # デフォルトは private end class C def bar # デフォルトは public end def ok # デフォルトは public end private :ok # …だが、ここで private に変わる def initialize # initialize は private end end
initialize
がprivate
? new
するときに呼ばれるよね? 直接initialize
という名前のメソッドを呼べないという意味かな?
private と protected は同じ目的(そのメソッドを隠し外から呼べないようにする)で使用されますが、以下のような例では、private は使えず、protected を利用する必要があります。正確には、private には関数を定義する目的があるが、呼び出し制限の目的でも(ここに挙げた制限があるにもかかわらず) protected よりは private が使われることの方が多いようです。
class Foo def _val 'val' end protected :_val def op(other) # other も Foo のインスタンスを想定 # _val が private だと関数形式でしか呼べないため # このように利用できない self._val + other._val end end
protected
はあまり使わない。デザインパターン的には継承より集約がよい。
定義に関する操作
alias
例:
alias foo bar alias :foo :bar alias $MATCH $&
文法:
alias 新メソッド名 旧メソッド名 alias 新グローバル変数名 旧グローバル変数名
メソッドあるいはグローバル変数に別名をつけます。メソッド名には識別子そのものか リテラル/シンボル を指定します(obj.method のような式を書くことはできません)。alias の引数はメソッド呼び出し等の一切の評価は行われません。
グローバル変数だけ? だったらalias $foo $bar
じゃないのかよ。ローカル変数で試してみよう。
a = 1 alias b a #=> undefined method `b' for class `Object' (NameError)
怒られた。ローカル変数に対してもalias
できないっぽい。
じゃあ次はグローバル変数でやってみる。
$a = 1 alias $b $a p $a #=> 1 p $b #=> 1
はいOK。ちなみに順序を間違えると全部nil
になる。
$a = 1 p $a #=> 1 alias $a $b p $a #=> nill p $b #=> nill
既存名から別名をつくるのだし、順序的には既存値が先かと思っちゃう。でも新しい名前から先に書くのが正しい。
メソッドの定義内で別名を付けるにはModuleクラスのメソッド Module#alias_method を利用して下さい。
めんどうくさい。使い分けなきゃいけないのか。そうまでして別名定義したいときってあるかな?
別名を付けられたメソッドは、その時点でのメソッド定義を引き継ぎ、元のメソッドが再定義されても、再定義前の古いメソッドと同じ働きをします。あるメソッドの動作を変え、再定義するメソッドで元のメソッドの結果を利用したいときなどに利用されます。
# メソッド foo を定義 def foo "foo" end # 別名を設定(メソッド定義の待避) alias :_orig_foo :foo # foo を再定義(元の定義を利用) def foo _orig_foo * 2 end p foo # => "foofoo"
ダッグタイピング的には別名定義できたほうが嬉しいときもあるってことなのかな?
グローバル変数の alias を設定するとまったく同じ変数が定義されます。このことは一方の変数への代入は他方の変数にも反映されるようになることを意味します。
# 特殊な変数のエイリアスは一方の変更が他方に反映される $_ = 1 alias $foo $_ $_ = 2 p [$foo, $_] # => [2, 2] $bar = 3 alias $foo $bar $bar = 4 p [$foo, $bar] # => [4, 4]
つまり実体は同じものを指すってことか。同一実体のポインタ変数を複数つくるのがalias
ってことね。
ただし、正規表現の部分文字列に対応する変数 $1,$2, ... には別名を付けることができません。また、インタプリタに対して重要な意味のあるグローバル変数 (変数と定数 を参照)を再定義すると動作に支障を来す場合があります。
ふーん。
alias 式は nil を返します。
へー。
undef
例:
undef bar
文法:
undef メソッド名[, メソッド名[, ...]]
メソッドの定義を取り消します。メソッド名には識別子そのものか リテラル/シンボル を指定します(obj.method のような式を書くことはできません)。 undef の引数はメソッド呼び出し等の一切の評価は行われません。
できるのはわかった。でも、いつやるの? なぜやりたいの? どんないいことがあるの? メモリが解放されるとか?
メソッドの定義内で定義を取り消すにはModuleクラスのメソッド Module#undef_method を利用して下さい。
またかよ。使い分けなきゃいけないの面倒。
undef のより正確な動作は、メソッド名とメソッド定義との関係を取り除き、そのメソッド名を特殊な定義と関連づけます。この状態のメソッドの呼び出しはたとえスーパークラスに同名のメソッドがあっても例外 NameError を発生させます。 (一方、メソッド Module#remove_method は、関係を取り除くだけです。この違いは重要です)。
え、undef
とremove
があるの? それ使い分ける理由がどこにあるの? 例外が発生するかどうかの違いがあるように読み取れるけど。例外が発生しないremove
だとどうなっちゃうんだろう。
Module#undef_methodのコード例に書いてあった。ちょっと変えて試してみると以下のようになった。
class A def ok; :A; end end class B < A def ok; :B; end end p B.new.ok # => :B # undef_method の場合はスーパークラスに同名のメソッドがあっても # その呼び出しはエラーになる class B undef_method :ok end #p B.new.okundefined method `ok' for #<B:0x01d2c2d8> (NoMethodError) class C < A def ok puts 'C' end end # remove_method の場合はスーパークラスに同名のメソッドがあると # それが呼ばれる class C remove_method :ok end p C.new.ok # => :A
スーパークラスにあってもエラーになる。というか、スーパークラスの同名メソッドさえも未定義にしている? いや、ドキュメントの説明では「特殊な関係にしている」だったっけ。まあとにかくこうなる。
alias による別名定義と undef による定義取り消しによってクラスのインタフェースをスーパークラスと独立に変更することができます。ただし、メソッドが self にメッセージを送っている場合もあるので、よく注意しないと既存のメソッドが動作しなくなる可能性があります。
元のメソッドをalias
で退避させておくってことかな? そんなことしてどうするんだって感じだが。ただただややこしくなるばかりでメリットがなさそう。
undef 式は nil を返します。
defined?
例:
defined? print defined? File.print defined?(foobar) defined?($foobar) defined?(@foobar) defined?(Foobar)
文法:
defined? 式
式が定義されていなければ、偽を返します。定義されていれば式の種別を表す文字列を返します。
末尾?
のメソッドは真偽値を返すんじゃなかったんかーい。さっそく裏切られた。いや、たしかnil
とfalse
以外はすべて真と判断するんだっけ? でもそれとこれとは別の話じゃないのか? そんなこといったらほぼすべてのメソッド末尾に?
をつけるハメになりそうだし。
定義されていないメソッド、undef されたメソッド、Module#remove_method により削除されたメソッドのいずれに対しても defined? は偽を返します。
はい。
特別な用法として以下があります。
defined? yield
yield の呼び出しが可能なら真(文字列 "yield")を返します。 Kernel.#block_given? と同様にメソッドがブロック付きで呼ばれたかを判断する方法になります。
またブロックかよ。おまえのせいでムダに複雑になりつづけているな。
defined? super
super の実行が可能なら真(文字列 "super")を返します。
トップレベルでやったらnil
が返ったんですけど。たしかにnil
もfalse
と判断するって聞いていたけども。偽のときですらfalse
ではないのか。もうわざとダイレクトな真偽値を避けていないか?
defined? a = 1 p a # => nil
"assignment" を返します。実際に代入は行いませんがローカル変数は定義されます。
でたー。宣言だけされて代入されないパターン! 書いているコードとの差異があって罠になりうる。ていうか定義されているかどうかを返すんじゃないの? なんかもうメチャクチャだな。defined?
の使いどころもわからないし、使い方もよくわからんし。覚えられなさそうだし。
/(.)/ =~ "foo" defined? $& # => "global-variable" defined? $1 # => "global-variable" defined? $2 # => nil
$&, $1, $2, などは直前のマッチの結果値が設定された場合だけ真を返します。
そっすか。
def Foo(a,b) end p defined? Foo # => nil p defined? Foo() # => "method" Foo = 1 p defined? Foo # => "constant"
大文字で始まるメソッド名に対しては () を明示しなければ定数の判定を行ってしまいます。
前にもそんなこと言ってたよね? もうメソッド名は小文字でいいじゃんか。
以下は、defined? が返す値の一覧です。
"super" "method" "yield" "self" "nil" "true" "false" "assignment" "local-variable" "local-variable(in-block)" "global-variable" "instance-variable" "constant" "class variable" "expression"
使いどころがわからん。実行時に定義されているかどうか判断できればいいって感じかな? エラーになるのを回避するif
文を書いて。というかif
文すら書いてない。そういうふうに書けるんだ?
対象環境
- Raspbierry pi 4 Model B
- Raspberry Pi OS buster 10.0 2020-08-20 ※
- bash 5.0.3(1)-release
- Ruby 3.0.2
$ uname -a Linux raspberrypi 5.10.52-v7l+ #1441 SMP Tue Aug 3 18:11:56 BST 2021 armv7l GNU/Linux