レシーバとは、メソッドを呼び出したオブジェクトのことである。car.run()
のうち左側のcar
を指す。
説明なきレシーバ
Rubyの公式ドキュメントでも当然のように説明がなく出てきたワードに「レシーバ」がある。文脈からしてins.method()
のコードのうち.
の左側のことを指しているのはわかった。けれど私にはmethod()
メソッドを呼び出したins
は主語のように思える。なにせins
インスタンスがmethod()
メソッドを所持している主体なのだから。それなのにレシーバ呼ばわりされるのがどうもしっくりこない。まるでメソッドのほうが主体であり、それを呼び出したインスタンスがオマケであるかのようだ。なぜレシーバと呼ばれるのか調べた。
英語
レシーバのスペルはReceiver
であり、意味は「受取人」である。何かを受け取る者のことだ。
レシーバ
「ins.method()
のins
はmethod()
メッセージを受け取る者である」という意味になる。
オブジェクト指向は一般的にメッセージのやりとりで処理を行うものだ。そのメッセージとはメソッドのことを指している。そうした文脈をもつオブジェクト指向言語としては、メソッドを呼び出したインスタンスのことをレシーバと呼ぶ。
メッセージってなんぞ?
たしかに上記のように説明されたら「受け取る者」と呼ぶのはわかった。
けれど、そもそもメッセージってなんすか? って話。いやいやメソッドじゃないのかよ、と。
関数でもメンバ関数でもメソッドでもない
メッセージは関数でもメンバ関数でもメソッドでもない。それらを抽象化した概念を指すのだと思う。
関数などの用語は言語によって異なる。
言語 | 用語 |
---|---|
C言語 | 関数 |
SQL/PL | プロシージャ |
C++ | メンバ関数 |
Java | メソッド |
また、C言語の関数とC++のメンバ関数は異なる。C言語は手続き言語であり、単に処理のまとまりを呼び出すだけだ。それに対してC++のメンバ関数は、インスタンスに紐付いた関数である。つまりJava,C#,Rubyなどでいうところのメソッドと同じだ。
このように言語によって「処理のまとまりを呼び出す」という共通部分はあるが、インスタンスがあるかどうかの違いがある。そういったことを覆い隠して抽象化したのがメッセージなのではないか。私はそう読み取った。
メッセージとは、他のオブジェクトのメソッドを呼び出すものである?
自分のメソッドだけではなく、他のオブジェクトのメソッドを呼び出すものらしい。
メッセージの中には、自分のメソッドだけではなく他のオブジェクトのメソッドを呼び出す処理もある。けれど表面上はメッセージだけであり、その中身は隠蔽されている。もし内部の仕様や処理が変更されてもメッセージは同一。なのでメッセージを呼んでいるところは変更せずに済む。
オブジェクト指向言語では、他のオブジェクトのメソッドを呼び出すものも同じくメソッドと呼ぶはず。ただ、オブジェクト指向の概念的にはメッセージという用語で抽象化したほうがいい理由があるのだろう。その理由が「変更容易性」といっているが、そこがよくわからなかった。多態性のことでありカプセル化のことであるが、それらがすべてではないということらしい。はあ? よくわからん。まあいい。
とにかく「メッセージとは細かい処理や呼出をひとつにまとめて抽象化したもの」ということ。大雑把にそういうもんだと思っておこう。
それに加えて「メッセージにはレシーバがある」ものなのだろう。それがオブジェクト指向言語であるならば。
URLの以下コメントがヒントになるかも。
インタプリタやコンパイラなどの言語処理系は けっきょくレシーバまで含めた情報を受け取らないと メソッドを区別できず、呼び出しを振り分けようがないので、 少なくとも言語処理系内部の仕組みはそうなっているはずです。 じゃあそれをメッセージという言葉でそのまま使おうと。
Pythonから考える
レシーバはメッセージを受け取る者のことだった。
たぶんPythonでいうself
なのだろう。 classのインスタンスメソッドを定義するとき、必ず第一引数にself
がいる。self
はレシーバなわけだ。インスタンスメソッドが呼び出されたとき、呼び出したインスタンスは、そのメソッドの第一引数に渡される。そしてメソッド定義内では、その第一引数のインスタンスをself
として受け取る。そしてself
からそのインスタンスがもつほかのメソッドも呼び出せる。
class C: def method1(self): self.method2() def method2(self): print('method2') C().method1()
C()
でインスタンス生成する。そしてmethod1()
を呼ぶ。このときレシーバとしてC()
が渡される。method1()
の処理をみるとself.method2()
とある。受け取ったレシーバからメソッドを呼び出している。
C().method1()
だとC()
は呼び出した主なので主語っぽい。def method1(self)
からみると渡されたC()
は引数なのでオマケっぽい。このことからC()
はみるところによって立場が変わるのだろう。
ではC()
がレシーバ(受け取る者)という立場になるときはいつなのか? おそらくソースコードを解析しているときだろう。なのでソースコード上からはみえないところでレシーバといえるような立場になっているのだと思う。たぶん「どのメソッドを呼ぶか特定する」段階で、C()
はレシーバと呼ぶにふさわしい立場になるんじゃないかな? method1()
というメッセージを受け取った者はC()
だよ。じゃあ次はC()
のもっているメソッドを呼び出そう。とか? うーん、ダメだわからん。たぶんソースコードを解析する処理を具体的にみないと感覚がつかめない気がする。
情報源
所感
そもそもRubyって途中でクラスの定義を変更できてしまう。だから他のJavaやC#などといったオブジェクト指向言語とも違うわけで。そんなことをいったら他の言語だって独自で。でもたしかに共通するところもあって。
全体像をざっくり掴みたいという動機はあるのだが、理解するためには詳しく突っ込む必要があり、そうすると言語ごとの違いが出てくる。結局は言語ごとの仕様を把握するしかない。わざわざ大雑把な概念を理解する必要ってあるの?
レシーバとうい語はオブジェクト指向の概念をあらわすときに使われた語だと思われる。Rubyはそれを使っただけ。深く考える必要はない。
レシーバはメソッドを呼び出したインスタンスのことである。
以上。おわり。これ以上はカオスになるから考えない。さようなら。
名称 | 言語をまたいで共通するであろう大まかな意味 |
---|---|
関数 | レシーバがない処理のまとまり |
メソッド | レシーバがある処理のまとまり |
メッセージ | メソッドのうちエンドユーザがみるシステム最上位のメソッド |
対象環境
- 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