今回のエントリーは、 Sass の機能説明というよりコラムのようなものになります。Sass の継承機能にまつわる考察および実験結果を、オブジェクト指向についての本は数冊読み、かつその他 C++ などの言語を扱った経験から若輩ものではありますが書いてみようと思います。
先に結論を書きますが、 Sass はオブジェクト指向ではありません。なぜなら、オブジェクト指向として必要な機能が欠けているためです。
オブジェクト指向とは何か?
オブジェクト指向とは、平たく言えば「ただの処理のまとまり」ではなく、「現実に沿ったモノとしての処理のまとまり」をプログラミング言語で表すというものです。例えば、リモコンをプログラミングで再現しようとしたらどうなるでしょうか。
リモコンには、電源 ON / OFF ボタン、チャンネルを変えるボタン、赤外線でテレビに信号を送る機能、様々なものがセットになっています。この機能を全て一つのオブジェクトとして、関数や変数をひとまとまりにしてしまいます。すると以下のようなことが出来るでしょう。
// リモコンを作る class リモコン { 電源 ON / OFF ボタンを押した時の動作; チャンネルを変えるボタンを押した時の動作; 赤外線でテレビに信号を送る機能; } // リモコンを作り出す 家のリモコン = new リモコン; // ボタンを押して見る 家のリモコン->電源 ON / OFF ボタンを押す; // テレビがついた!
ほとんどのオブジェクト指向についての書籍では、この代表的な扱い方が大きく取り上げられています。これは一般的な解釈として間違いはありません。
同様のことを Sass で実装しようとしたら、以下の形を取ります。
@mixin button() { ボタンを押した時の動作; } ... .リモコン { button(); ... }
なるほど、確かにオブジェクト指向と言えそうです。しかし、オブジェクト指向に必要な機能はまだ他にあるのです。
オブジェクト指向の本質 – 処理を隠す
前記のリモコンの例をもう少し掘り下げて考えてみます。この「現実に沿ったモノとしての処理のまとまり」を実現する機能だけを見れば、なるほどと思ってしまいがちです。しかし、オブジェクト指向の本質はこれだけではありません。大事なことは、「処理を隠すこと」です。リモコンのオブジェクトは以下のようなこともできてしまいます。
// 赤外線を飛ばす 家のリモコン->家のテレビに赤外線を飛ばす; // ??
いったい何の信号を飛ばしたのでしょうか。本来はリモコンを扱う人から見えなくするべき処理です。しかし、現実ではこの赤外線を飛ばす機能は実在します。リモコンの中身を分解して、持っている値なども簡単に取得できてしまいます。全てさらけ出しているリモコンは、リモコンの機能は持っていますが私達の知っているリモコンではありません。
処理を隠す、という機能から見たSassとは
処理内容を隠す、という機能から Sass を見ると実装が不十分です。まず、 @mixin の定義自体を動的に隠すことはできません。
@mixin Class() { @mixin Object() {} }
@mixin はどこからでも呼び出せるように作らなければならず、隠ぺいすることは不可能です。同様に、以下のコードもコンパイルエラーとなります。
@if (true) { @import "remoconA"; } @mixin useClass() { @import "remoconB"; }
オブジェクト指向の本質 – 機能を継承する
前記のリモコンの例をとり、オブジェクト指向の継承のシステムをかいつまんで説明したいと思います。リモコンの外装に注目してください。リモコンの色は各メーカーや市販品を見れば形や色も様々ですよね。しかし、リモコンという基本的な機能は同じです。この概念をプログラミングで実装します。
// リモコンを作る class リモコン { 電源 ON / OFF ボタンを押した時の動作; チャンネルを変えるボタンを押した時の動作; 赤外線でテレビに信号を送る機能; } // 新製品のリモコン!かっこいい! class coolリモコン : public リモコン{ 外装や形を変える!; }
この新しいリモコンの機能は、前期のリモコンと同じ機能です。機能がコピーされています。しかし外装は違います。前に生産したリモコンの外装だけを上書きしているのです。今回の例では外装というパラメータを上書きしましたが、機能自体を上書きすることもできます。これが継承というシステムです。
機能の継承、という機能から見たSassとは
機能を継承する、という機能から Sass を見ても実装が不十分です。継承の機能を見る上で、 @extend に注目します。では継承の機能を実装できているのか Sass で見てみたいと思います。
.baseclass { font-size: 40px; color: orange; } .testclass { @extend .baseclass; // font-sizeが実装された color: blue; // 色をorangeからblueに上書き }
上記の例で分かりますが、ちゃんと追加、上書きが機能しています。では、CSS のプロパティではなく変数を見てみます。
.baseclass { $font-size: 40px; $color: orange; font-size: $font-size; color: $color; } .testclass { @extend .baseclass; @debug $font-size; // コンパイルエラー }
変数だと継承されていません。というより、そもそもプロパティの継承というのも Sass 自体の機能というより CSS の機能です。上記のコードの例から見ても、使いまわしたい値や、継承先のクラスのために残しておきたい値も継承できないことになります。もし、使いまわしたい値があるのであればグローバル変数として定義するしか方法はありません。それでは @mixin と使う理由が同じになるではありませんか。
@mixin baseclass() { $font-size: 40px; $color: orange; font-size: $font-size; color: $color; } .testclass { @include baseclass; color: blue; // 上書きする }
唯一 @extend と @mixin の違う点は、 @mixin を使った場合だと使用するまで中の処理は実行されないということです。 @extend だと、継承目的のクラスが(無駄に) CSS として出力されます。作ったクラスを読み込んでいるのであたり前のことですね。この、無駄に CSS を吐き出してしまう問題については解決策があります。解決策については今度のエントリーに譲ります。
Sassはオブジェクト指向ではない
オブジェクト指向として最低限必要な機能を備えられない以上、オブジェクト指向ではありません。やはり、 CSS のメタ言語としての枠からは外れていないようです。オブジェクト指向 & Sass というキーワードを見て過度な期待はされない方がいいと思います。私個人の感想ですが、そもそもそこまで複雑な言語にする必要があるのか?複雑なメタ言語に需要はあるのか?と言われると、必要です、と答えます。複雑かどうかより、再利用しやすい言語であってほしいからです。最近の Sass のアップデートもありましたし、今後に期待したいと思っています。
もし、「違うだろ」「そうではない」などのご批判を含め、何かありましたら Twitter にてお知らせください。