Traits: Composable Units of Behaviourを読んでサマリーを書きたかった人生だった。

プログラミングを学ぶ際に、プログラミング言語に関する論文を読む。

という概念を最近学んだので、いい感じにいい感じなやつを教えてもらって読みました。

メモ書き程度ですが、簡単に面白かったところを紹介します。

最初はサマリー書くぜー₍₍ (ง ˘ω˘ )ว ⁾⁾と思ってたけど、 大変すぎて大変だったのでやめておくことにしました。

しっかり理解してないと要約を書くのは難しいですね。(´・_・`)

trait

読んだのはこちら。

http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf

traitが提案された論文で、traitが生まれた経緯と狙いについて書いてあります。

traitmixinって似てない?っていうかmixinの方が良くなくないない? という方におすすめです。( ⁰⊖⁰)

論文では、単一継承多重継承mixin継承の問題点やtraitでは、それらがどう克服されているかが紹介されていますが、今回はmixintraitの違いに焦点を当てて紹介したいと思います。

クラスやばい問題

多重継承には実装の問題があることはよく知られていますが、 さらにクラスという単位が再利用の単位として最適ではないケースが頻繁にあることも問題だそうです。

論文ではクラスには2つの主要な役割があるとされています。

generator of instancesインスタンス生成)とunit of reuse(再利用の単位)です。

インスタンス生成を遂行するためには、クラスは完全である必要があります。 つまり、それ単体で動作する必要があります。

一方、再利用の単位としては、できるだけ小さくまとめたいという要求があります。

これらは競合する目的ですので、コードの重複を取り除いて再利用したい!といった時は、 やや難しいというケースがあるそうです。

しかもこれらをクラスの階層構造(親クラス・子クラスの関係)の構築をやりながら行っているわけですから、 機能多すぎですね。(って友人が言ってました。たしかにやばい。(´・_・`))

mixin inheritance

クラスやばいから、単独で動作する必要なくない?といった具合に、

サブクラスによって継承されることにより機能を提供し、単体で動作することを意図しないクラス

via wikipedia

を作ったのがmixinです。

mixinでは多重継承のようなことが出来ますが、その継承は線形化されます。

論文にのっている例(Fig2のところ)を紹介します。
Rectangleクラスに対して、MColorと、MBorderの持っている機能を付けたいとします。

mixinではまずRectangleMColorをmixinし、その後Rectangle + MColorMBorderをmixinします。

このように順番をつけてmixin継承を行うと、

Rectangle > Rectangle + MColor > Rectangle + Mcolor + MBorder

のような関係ができあがります。(小さいほうがサブクラス)

このようにしておくとMColorMBorderが同じメソッドを持っていても(例えば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は単純なメソッドの集合です。

traitclassや別のtraitに含ませる(useする)ことができます。

また以下の様な特徴を持っています。

  • traitがクラスでuseされていても、それはクラス階層に影響を与えません。
  • trait経由でメソッドが定義されていても、それはクラスに直接定義してあるものと区別されません。
  • traituseするtraitがあっても、それらは階層を構築せずフラットな状態になります。(後述)

クラス階層に影響を与えないのはmixininterfaceと大幅に異なる点だと思います。

みんな大好き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にメソッドが追加されて衝突が発生したらエラーがちゃんと出てくれますし、 メソッドが削除されてもエラー(bigTalkAのやつを使うって言ってるけど無いやんけ!みたいなエラー)がちゃんと出てくれます。

このような特性を持ったtraitを用いることで、メソッドの実装をクラスの階層構造と切り離して考えられるようになります。

そして、メソッドの集合であるtraitを小さい単位で作ることが可能になり、 コードの再利用性を向上させることができるようになるそうです。

traitを使おう

trait同士の連携部分や状態の管理などはクラスに残りますが、コードの再利用の単位としてtraitを採用することで、重複が少なく階層が複雑化しないコードが書けるようになりそうです。

クラスにはなるべくクラスの階層構造の構築やインスタンス生成に集中してもらいましょう!

なお論文の最後の方ではtraitという名前がついた別のアイデアや 他の関連するアイデアの紹介があります。が、そちらは読んでいません。(´・_・`)

本記事ではmixinの問題点のみを指摘しましたが、traitが最強でmixinは雑魚というわけではありません。たとえばtraitは状態を持つことができない・クラスの構造に組み込めないので型をどうすればよいか、といった制約や疑問が存在します。

しかし、traitが克服しようとした問題点を理解することによって、traitを利用したより効率的なプログラミングにつながるのではないかと思いました。

がんばろう( ⁰⊖⁰)