トレイトのフラッティング

Traits は、inline関数や Cのマクロに似ています。

Traits を クラスに use した際に、継承や委譲と違い、メソッドの持ち主を指し示すのではなく、メソッドそのものをクラスに展開してしまいます。これは通常のコール/リターンする関数と、 inline関数や Cのマクロ の間柄によく似ています。

これを「フラッティング」と呼ぶのですが、たいていは実効効率 の問題で用いられる inline関数と違い、Traits の場合「フラッティング」は可読性の為だと言います。

Even though a class may have been constructed by composing small traits in a complex hierarchy, there is no need to require that it be viewed in the same way. It should be possible to view the class either as a flat collection of methods or as a composite entity built from traits. The flattened view promotes understanding; the composite view promotes reuse. There is no conflict so long as both of these views can coexist, which requires that composition be used only as a structuring tool and have no effect on the meaning of the class.


あるクラスが小さなトレイトの複雑な階層として構成されていたとしても、クラスを見る際にもそのような形式で見る必要はありません。クラスは「メソッドのフラットなコレクション」としても「トレイトで形作られた複合エンティティ」としても見られようになっているべきです。両方の視点が共存できる限り(再利用性と解りやすさの)競合はありません。そのためには構成(composition)が構造化のツールとしてのみ使われていて、クラスの意味に影響を与えない必要があります。

(るとさん訳。本当にありがとうございます)


inline関数の場合、その主な動機は 共通化(修正を閉じる..一カ所修正したら全てに適応される)と抽象化(見た目のコード規模を減らす)をしつつ、ベタ書きコードと同様の実効効率を得ることが目的でした。

ところが Traits の場合、共通化を得つつも抽象化については放棄しています。Traits を use することで クラス中には メソッドの山がゴチャッとぶちまけられる訳です。誤解を恐れずに言うならば、Traits の設計者たちは、抽象化は解りにくいと断じたのだと思います。

Traits の フラッティングは、その「ゴチャッ」と展開されたメソッドを、プログラマの目に(まるで直接クラスに定義したかのように)見せるのが目的です。


* * *


皮肉なことに、テキストエディタ(や統合開発環境中のテキストビュー)で編集するような プアでプリミティブな プログラミング言語環境では、トレイトのフラッティングはせいぜい、「継承したメソッドはわざわざsuper を辿らなくても勝手にコールできる」と同じ効果しかありません。

プログラマはリフレクションを眼鏡に対話的にプログラムをビルドアップせず、設計図を書き上げるフェーズと設計図を元にプログラムが組み立てられ動くフェイズに明確なモードがある、ごく普通のプログラミング言語と違い、Smalltalk 環境下では、実行中の環境(プログラム)の中に、プログラムは実行されたまままずクラスを作り、できたクラスに同じくプログラムは実行されたままメソッドを追加していくスタイルと取ります。

たとえば、Trait TFoo (↓)

があったとします。さて、Class Xyzzy を定義すれば(↓)

当然その中身は空っぽです。(メソッドが入っていない)

なのに、Xyzzy の定義をちょっと変えて uses TFoo したとたん、(↓)のように

Traits の持ちモノであったハズのメソッド(sayHello)がクラスにも作られているのです。

たとえSmalltalk であっても、継承元のメソッドを自身のメソッドのようには見れません。継承階層を辿り、真にそのメソッドをもっているクラスをブラウザで開かなければ見れません。

逆に言えば、ブラウザでそのクラスに見えているメソッドは真にそのクラスの持ち物です。基底クラスに同名のメソッドがあったとしても、あぁ、オーバライドされてるんだな、とか思います。

でもTrait の use の場合、それが Trait 由来なのか、それとも自分でオーバーライドしたものかの区別は(今のところ)付きません*1。というか そういう区別が目に見えない(目に見えにくい)方が「解りやすい」という思想が Traits なのだと思います。


* * *


フラットに展開されたほうが解りやすいか否かは、微妙です。わたしは抽象化大好きっ子なので、はっきり言ってフラッティングは余計事にしか思えません。

でも、「で、結局このクラスに送れるメッセージななんなのよ」と言うときとか、Template Method パターンのConcreteClass だけを掴まされるとき(よくある GUI フレームワークとか)、動作のわかりにくさに涙を呑みます。(こういうとき、リファレンスの出番となるのが「覚えとけ」的で腹立たしくもあったり)

もっとさかのぼれば、「関数でアチコチ処理が跳ぶので解りにくい。全部その場で展開しといてくれ」と言ってしまうのも 人間なのでしょう。フラッティングはそう言う「解りやすさ」の一側面への福音です。

一方で、わたしは Traits には、丁度 CRCカードの「責務」のレベルで抽象化を行える カプセルになって欲しいとも思います。既存のクラスは実用的に作り上げるとメソッドが増えすぎだと思います。メソッド数が両手の指で数えられる数を超えたら、もう人の頭の扱える複雑さを超えているとわたしは思います。こういうレベルでクラス間の役割を考えるとき、「責務」の粒度が丁度良いのです。

で、話は Perl にワープするのですが、Perl の Traits は Roles と言います。


* * *


The Why of Perl Roles (anatooさんによる日本語訳) によると、「ロール」とは

A role is a name for a discrete collection of behaviors.


roleとは、分離された振る舞いのコレクションの事である。

です。大切なのは、メソッドのセットそのものではなく、それをセットに分離することにより、どのような抽象を得られるかです。

The important question is not "Do you have a method called log_debug_output?" or "Do you inherit from DebugLogger?" but "Do you perform the DebugLogging role?"


重要な疑問は、「log_debug_outputというメソッドを持っているか」、「DebugLoggerを継承しているのか」といったものではなく、「DebugLogginのroleを果たすのか」である。

これは、Java では Interface として表現され、動的OOPL ではダッグタイピングの中で暗黙に示されます。問題はこれらにどうやって「ロール」に実装を与えるかです。

その答えが Roles です。

Roles can provide default implementations of behavior they require. By composing a role into a class, you can import its methods (and other attributes) into the class directly at compile time. There are rules for disambiguation and conflict resolution and requirements and provisions, but you get the rough idea. A role also provides a mechanism for code reuse.


roleは必要とする振る舞いのデフォルト実装を提供できる。roleをクラスに合成することによって、メソッドやその属性をコンパイル時に直接クラスへとインポートできる。曖昧性の除去と衝突の解決と必要資格と規約に関するルールがあるが、あなたはぼんやりとした考えを持つだろう。それからroleはコードの再利用のためのメカニズムを提供する。

(強調はわたし)

Roles は 「ロール」のデフォルトの実装を与えます。そう、動的に戦略を切り替えたりなんてしないのに、ただ開放閉鎖原則を目指すために Strategy パターンを使っていたシチュエーションを スマートかつエレガントに差し替えます。ちまちまと委譲コードを書いていた部分も勝手にクラスにインターフェイスを開けてくれます。

なんて素晴らしい。凄いよ! Roles。 もう惚れてしまいそうです!

なのに、これがこと Traits となると。Smalltalkの中に入るとなると、どうしてこうも モヤモヤしてしまうのか。


* * *


その答えは簡単で、それは Traits がフラッティングされてしまうこと。それがどうやら実装上の都合ではなく、論文をよめば 解りやすさの為だと言うこと。そこに今一歩納得が追いつかないのです。

クラスの定義文をみれば、そのクラスがどのような Trait(特性、あるいは Role/ロール) を持つのかハッキリする。でもメソッドを舐めるときに Traits はまったく意識されない透明の存在になる。これが素晴らしいことなんだよ、と論文には書いてあります。けれど、わたしはそれが全然ピンと来ない。

だって、今だってプロトコル(メソッドカテゴリ)が必要なんだよ。わたしはプロトコルが大好きで、クラスとメソッドの間にもう一つみためをスッキリさせる為のカプセルが必要だと思ってます。

クラスをメソッドのコレクションとして見るシチュエーションにおいて、どのメソッドも それが Traits を使って構築されていることに気付かれてはいけない、Traits 透明な存在であるべき ・・・に Squeak 4.1 のクラスブラウザは忠実です。

実際全く気がつきません。複数 Traits を uses したときになんて、まぜこぜで本当にわかりません。本当は大本のTrait側の定義を修正すべきでも、クラスをみれば底に定義されているように見えるので、意図せぬオーバーロードしちゃいます。それぐらい徹底して透明です。


* * *


オーバーロード事故とか Traits がメソッド注目時に常に透明になってしまって解りにくいのこかは、単なる 見せ方の問題です。また Traits という箱自身は、Perl の Roles の説明を読むとワクワクしてしまうように、基本的に良いモノだと思います。use 時の メソッド取捨やエイリアスが出来る当たりは非常に実用的で便利そうです。

基本は気になるTraitsさん。

でも、Squeak で、Traits に修正があったときにuseするクラス全部にその修正を波及させる仕組みとか、超頑張ってるのを見てしまうと。そして頑張らないで、Traits のモデルを馬鹿正直にそのまま乗っけただけの GNU Smalltalk のダメダメさ加減を見ていると。

所詮見せ方の問題と割り切るならば、フラッティング(と、構造化ビューとフラットビューを両立させるメンタルモデルとしての、責務の移譲先の参照ではなく、メソッドそのものをクラスにコピーして流し込む実装)って、本当に良いモノなの?と、じと目になってしまいます。でも、そういう「しっくりこないんです」症候群なときは、対象がダメなケースよりも、自身が理解できていないケースのほうが圧倒的に大多数なのですから、きっと自分は Traits が解ってないにだけなんだろうな、とも考える。

――というのが、わたしのモヤモヤの正体じゃないかしら、と、いろいろアドバイスやコメントを頂いたおかげで、漸くここまで絞れたと思うのですが(明後日を向いただけだったらどうしよう^^;)、どうでしょうか?

*1:でも、今のままだとうっかりオーバーライドによる意図せぬフォークをやっちゃいガチなので、そこら辺の挙動は将来は使いやすいように変わると思います