プログラミングを学ぶ際に、プログラミング言語に関する論文を読む。
という概念を最近学んだので、いい感じにいい感じなやつを教えてもらって読みました。
メモ書き程度ですが、簡単に面白かったところを紹介します。
最初はサマリー書くぜー₍₍ (ง ˘ω˘ )ว ⁾⁾と思ってたけど、 大変すぎて大変だったのでやめておくことにしました。
しっかり理解してないと要約を書くのは難しいですね。(´・_・`)
trait
読んだのはこちら。
http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf
trait
が提案された論文で、trait
が生まれた経緯と狙いについて書いてあります。
trait
とmixin
って似てない?っていうかmixin
の方が良くなくないない?
という方におすすめです。( ⁰⊖⁰)
論文では、単一継承
や多重継承
、mixin継承
の問題点やtrait
では、それらがどう克服されているかが紹介されていますが、今回はmixin
とtrait
の違いに焦点を当てて紹介したいと思います。
クラスやばい問題
多重継承には実装の問題があることはよく知られていますが、 さらにクラスという単位が再利用の単位として最適ではないケースが頻繁にあることも問題だそうです。
論文ではクラスには2つの主要な役割があるとされています。
generator of instances
(インスタンス生成)とunit of reuse
(再利用の単位)です。
インスタンス生成を遂行するためには、クラスは完全である必要があります。 つまり、それ単体で動作する必要があります。
一方、再利用の単位としては、できるだけ小さくまとめたいという要求があります。
これらは競合する目的ですので、コードの重複を取り除いて再利用したい!といった時は、 やや難しいというケースがあるそうです。
しかもこれらをクラスの階層構造(親クラス・子クラスの関係)の構築をやりながら行っているわけですから、 機能多すぎですね。(って友人が言ってました。たしかにやばい。(´・_・`))
mixin inheritance
クラスやばいから、単独で動作する必要なくない?といった具合に、
サブクラスによって継承されることにより機能を提供し、単体で動作することを意図しないクラス
via wikipedia
を作ったのがmixin
です。
mixin
では多重継承のようなことが出来ますが、その継承は線形化されます。
論文にのっている例(Fig2のところ)を紹介します。
Rectangleクラス
に対して、MColor
と、MBorder
の持っている機能を付けたいとします。
mixinではまずRectangle
にMColor
をmixinし、その後Rectangle + MColor
にMBorder
をmixinします。
このように順番をつけてmixin継承を行うと、
Rectangle > Rectangle + MColor > Rectangle + Mcolor + MBorder
のような関係ができあがります。(小さいほうがサブクラス)
このようにしておくとMColor
とMBorder
が同じメソッドを持っていても(例えばasString
)、
メソッド呼び出しの際には、Rectangle + Mcolor
にあるasString
メソッドがMBorder
によって
オーバーライドされているので、呼び出し時に混乱しないよね(・ω<)といった効果が得られます。
rubyで書くとこんな感じです。
class Rectangle def asString "I'm Rectangle!" end end module MColor def asString "My Color is Blue!" end end module MBorder def asString "Bold border!" end end # 一気にmixin継承を行っているが、mixin継承に順番がつけば良い。 class MyRectangle1 < Rectangle include MColor include MBorder end # mixinの順番が異なる class MyRectangle2 < Rectangle include MBorder include MColor end r1 = MyRectangle1.new r2 = MyRectangle2.new p r1.asString # "Bold border!" p r2.asString # "My Color is Blue!"
しかしtrait
mixin
最強!ヤッタ―!と思いきや、最強ではありませんでした。(´・_・`)
前節の例だと、基本はMBorder
でオーバーライドして欲しいけど、asString
に関してはMcolor
のを使いたいんやけど〜 といったケースの際に困ったことになります。
他にも、たくさんmixin
したいい感じのクラスを作ってしまうと、依存しているmixin
クラスのうちの一つにメソッドを追加したり削除した際に問題になります。
意図しないオーバーライドが発生してよくわからない挙動になったり、オーバーライドされていたはずなのに、削除されたせいで別のメソッドがいつの間にか呼ばれてる・・・ といった不安定な挙動に悩まされる危険性があります。
以上のような問題点を踏まえて、trait
が考案されました。
trait is 何
trait
は単純なメソッドの集合です。
trait
はclass
や別のtrait
に含ませる(use
する)ことができます。
また以下の様な特徴を持っています。
trait
がクラスでuse
されていても、それはクラス階層に影響を与えません。trait
経由でメソッドが定義されていても、それはクラスに直接定義してあるものと区別されません。trait
をuse
するtrait
があっても、それらは階層を構築せずフラットな状態になります。(後述)
クラス階層に影響を与えないのはmixin
やinterface
と大幅に異なる点だと思います。
みんな大好きPHPで書くとこんな感じです。
<?php trait Hello { public function sayHello() { return "I'm hello"; } } class Greeter { public function sayHello() { return ""; } } class MyGreeter extends Greeter { use Hello; } $g = new MyGreeter(); echo $g->sayHello(); // I'm hello
衝突の解消
trait
を複数使った際にメソッドが衝突した場合は、
どのtrait
のメソッドを使うのかを明示的に宣言して解決します。
(これをしないとメソッドが重複しているというエラーになる。)
再びPHPです。 PHP: トレイト - Manualの例をちょっと変えた物を紹介します。
<?php trait A { public function smallTalk() { return "a"; } public function bigTalk() { return "A"; } } trait B { public function smallTalk() { return "b"; } public function bigTalk() { return "B"; } } class Talker { use A, B { B::smallTalk insteadof A; A::bigTalk insteadof B; } } $t = new Talker(); echo "{$t->smallTalk()}\n"; // b echo "{$t->bigTalk()}\n"; // A
衝突の解決後、trait
はフラットな構造になります。
つまり、あるメソッドがどこで実装されていたかは区別されなくなり、
単に自分の中で実装されているものと同じ扱いになります。
フラットな構造を持つtrait
を利用するとmixin
で問題になった、
「線形化されてるけど一個前のメソッドが使いたいんだよね〜」といったな悩みが解消されます。(そもそも線形化されていないので、このtrait
の物を使います!と宣言可能。)
また、明示的な解決が必要なので、あるtrait
にメソッドが追加されて衝突が発生したらエラーがちゃんと出てくれますし、 メソッドが削除されてもエラー(bigTalk
はA
のやつを使うって言ってるけど無いやんけ!みたいなエラー)がちゃんと出てくれます。
このような特性を持ったtrait
を用いることで、メソッドの実装をクラスの階層構造と切り離して考えられるようになります。
そして、メソッドの集合であるtrait
を小さい単位で作ることが可能になり、
コードの再利用性を向上させることができるようになるそうです。
traitを使おう
trait
同士の連携部分や状態の管理などはクラスに残りますが、コードの再利用の単位としてtrait
を採用することで、重複が少なく階層が複雑化しないコードが書けるようになりそうです。
クラスにはなるべくクラスの階層構造の構築やインスタンス生成に集中してもらいましょう!
なお論文の最後の方ではtrait
という名前がついた別のアイデアや
他の関連するアイデアの紹介があります。が、そちらは読んでいません。(´・_・`)
本記事ではmixin
の問題点のみを指摘しましたが、trait
が最強でmixin
は雑魚というわけではありません。たとえばtrait
は状態を持つことができない・クラスの構造に組み込めないので型をどうすればよいか、といった制約や疑問が存在します。
しかし、trait
が克服しようとした問題点を理解することによって、trait
を利用したより効率的なプログラミングにつながるのではないかと思いました。
がんばろう( ⁰⊖⁰)