多重継承禁止とインターフェース, Mix-in の許可

多重継承の問題と言語ごとの解決策に関する話を書いてみました。
ただ、 Ruby 好きのため、若干 Ruby よりの記述になっています。

多重継承の問題と Object クラス

多重継承

多重継承で問題になるのはメソッド名などが重複した場合です。
特にダイヤモンド継承(ひし形継承)と呼ばれる共通の祖先を持つクラスの継承では、 必ずメソッド名の重複は起こりますし、どっち経由の祖先のメソッドかわからなくなります。
ダイヤモンド継承
多重継承が許されている言語としては C++ や Python などがあります。
C++ では virtual な継承を使ったり、 Python では書いた順でメソッドの優先度を決めたり といった方法で共通祖先の継承をできるようしています。

しかし、どのメソッドを使うかの問題が解決しても、 共通のメンバー変数(属性)を別々のクラスのメソッドから入り乱れてアクセスすることになり、 不測の問題を引き起こしがちです。

ちなみに JavaScript はプロトタイプベース、 Google が開発した Go 言語では匿名フィールドといった感じで、これらは継承のシステム自体ちょっと違います。 ただ、多重継承っぽいこともできて、やれば似たような問題を引き起こします。

Object クラス

共通の祖先を持たないように多重継承すればいいかというと、 これもやりたくない事情があります。

C# や Ruby といった言語では 全てのクラスが共通の一つの基底クラスから派生しています。 この基底クラスはたいてい Object という名前になっています。
Object クラス
一つのクラスから派生することによって色々なメリットがあります。 その一つとしては ToString() のような全てのクラスが共通して持つメソッドを作れることです。
例えば、 C++ から C# では printf 系での出力位置の指定の仕方が変わりました。
    [C++] printf("%s 。 %d 時です\n", "こんにちは", 12);
    [C#]  Console.Write("{0}。 {1} 時です\n", "こんにちは", 12);
こういったことができるのも、整数など基本型を含めたすべてのクラスが文字列への変換関数を持っているからです。

一つのクラスから継承するメリットは大きいため、 この方式を捨てることは難しいでしょう。 そうなってくると必然的に多重継承はダイアモンド継承になってしまいます。


そこで、"多重継承は問題を起こす、だったら多重継承を禁止しよう" ということになります。
しかし、全面禁止にしたくない理由もあります。 今度はそういった事情を考えてみましょう。

インタフェース

ジェネリックプログラミング

C++ で vector などのコンテナークラスをソートするとします。
 std::vector<MyClass> ary;
 std::sort(ary.begin(), ary.end());
このコンテナークラスに格納するオブジェクトのクラス(MyClass)に < 演算子 が定義されてさえいれば、ソートは可能です。 < 演算子というと C++ 特有感が強いですが、strcmp() のような 比較して数値を返す compare() メソッドが実装されている必要があると考えてください。

C++ のテンプレートのように、型を気にせず、 そのオブジェクトがメソッドをもっているかだけが重要になってくるのが、 ジェネリックプログラミングです。
C++ ではテンプレートを使った時だけジェネリックですが、 Ruby, JavaScript といったスクリプト系の多くは言語全般がジェネリックで、 関数定義などで型指定する必要がありません。

ただし、型指定が悪いわけではありません。 静的な型チェックはコードの安全面で大きなメリットです。
以前 Google が JavaScript の後継として Dart という言語を出してきました。 この Dart では JavaScript の改良として型指定が導入されました。

Go 言語 や PHP では関数の引数定義で型指定するかどうかで、 簡単に型チェック、ジェネリックを切り替えらるようになっています。 こういったスタイルが一番使いやすいのではないかと思います。

メソッド定義の保障

前節の C++ の sort の話で、 compare 関数( < 演算子 )が定義されていない型を渡すとどうなるでしょうか?
これは STL に残っている問題の一つで、かなり意味不明なコンパイルエラーが発生します。
ジェネリックなスクリプト言語の場合は実行時に compare() が呼ばれた時点で エラーとなります。

"sort() に渡す型は compare() が定義されていなければならない" ということが、ジェネリックプログラミングではドキュメントに書かれていて、 それをプログラマーが守って使うことになります。
これは C++ で const 演算子を使わず、 IN だの OUT だのといったコメントだけで、 使い方を示しているようなものです。 もっとコードで明示的に示され、 コンパイル時などでちゃんとチェックしてくれる方がいいでしょう。 これを実現するのが、インターフェースです。

Comparable interface
インターフェースとは振る舞いだけが定義されたクラスです。
引数の型として Comparable を指定すると Comparable を継承したクラス以外を渡した時点でエラーとなり、 関数の入り口でのチェックが可能となります。 また、インターフェースを継承するクラスでは compare() の実装を強制されます。

インターフェースは必要なメソッドが定義されていることの保障として役に立ちます。
それでいて、属性やメソッドの実体を持たないクラスなので、 多重継承しても問題は発生しません。
そのため、 Java, C#, Dart, PHP といった型指定できる言語では、 多重継承は問題が起きるので禁止するけど、インターフェースは許可する というのが主流になってきています。

Mix-in

概念

インターフェース以外にも 多重継承しても問題を起こさず、役に立つものとして Mix-in があります。 Ruby では多重継承は禁止されてますが、 Mix-in は使えるようになっています。

多重継承の例に挙げた鯨の話に戻って考えてみます。 哺乳類と魚を継承して鯨というよりも、 "泳げる" という機能を共に持っているとみなします。こちらの方がより現実に近いのではないでしょうか。 こういったクラスに機能だけを追加することを Mix-in といいます。
Mix-in

インターフェースでは、属性もメソッドの実装も持ちませんでしたが、 Mix-in では、 メソッドの実装はありますが、継承される属性を持ちません。
これにより、多重継承時の問題を回避しています。

Comparable

もっと実際的な話にしましょう。 インターフェースで出てきた compare() ようなキーとなるメソッドがクラスに定義されているとします。 このクラスに Mix-in をすれば キーメソッドから導出できる様々なメソッドが使えるようになります。
例えば、 Ruby では compare() に当たる <=> 演算子を定義して、 Comparable を Mix-in すると ==, <, <= といった比較演算子が全て使えるようになります。
つまり、自分で作ったクラスに対して簡単に比較の機能を追加できるわけです。
Mix-in を使えば、 compare() を実装するというインターフェースと同じ労力で、 より優れたクラスが作れるということです。
[Mix-in]     if ( a < b) {
[Interface]  if ( a.compare(b) < 0) {
演算子のオーバーロード自体に批判的な意見もあります。 しかし、めちゃくちゃに使えばもちろんわかりにくくなりますが、 適切に使えば直感的にも読みやすく、書きやすいクラスを作ることができます。

Enumerable

Ruby でもう一つよく使われる Enumerable を見ていきましょう。 これはコンテナーに機能を追加する Mix-in です。

N 分木のような自作のコンテナークラスを作ったとします。
Enumerable のキー関数は要素一つ一つに順にアクセスする each() というメソッドです。 この each() メソッドを持っていれば、 検索(find, include?, min), フィルター(collect), ソート(sort), 変換(map) といったコンテナークラスで欲しいメソッドが使えるようになります。
つまり、自作のクラスに each() を実装するだけで、 組み込みクラスさながらの便利なクラスにすることができるということです。

ただ、機能的にはインターフェース系の言語でも近いことはできます。 例えば、 find() といった関数を用意して、 そこに each() のようなメソッドが定義されたクラスだけを渡せるようにしておく といった具合です。
しかし、書き方が変わってきます。
[Mix-in]       ntree.find(5)
[Interface]    find(ntree, 5)
インターフェースでは find() の引数にコンテナーを渡すのに対して、 Mix-in ではコンテナーに find() の機能を付加します。
Mix-in ではよりオブジェクト指向的に書けると言えるでしょう。 このため、"純粋"なオブジェクト指向言語である Ruby で採用されいるのだと思います。

まとめ

インターフェースと Mix-in は決して排他的なものではありません。 Mix-in は言語の機能というよりも多重継承の使い方の一つなので、 C++ のように多重継承が許可されていれば、 Mix-in はやろうと思えばできます。

多重継承は問題を起こしがちなので、 禁止しようというのもその通りだと思います。 インターフェースだけ許可し、多重継承は禁止するというのが、 今の言語の主流のような気がします。
しかしながら、インターフェースだけしか許可していないと Mix-in は作れません。 非常に残念な傾向にあると言えるのではないでしょうか。

一方、 Ruby では多重継承を禁止し、かつ Mix-in は使えます。 "やっぱり、 Ruby って最高" と結論付けたいところですが、 Ruby を使っていて、型指定やインターフェースの必要性を感じないというわけでもありません。

私としては多重継承は禁止し、インターフェースと Mix-in だけ許可 というのが理想的なんじゃないかと思います。
実際、最近では Dart はインターフェースに加えて Mix-in が後から追加されましたし、 C# にも LINQ という Minx-in 風の機能が追加されました。 今後はこういった流れが増えてきてほしいと思います。



スポンサーサイト



 

Io 言語のインストール(Windows)と Emacs モードの設定

今回は Io の Windows へのインストール方法の紹介です。Emacs の io-mode についても書いています。

Io はプロトタイプベースのオブジェクト指向言語です。 結構、マイナーな言語ですが、 『7つの言語 7つの世界』 に載っていて、ちょっと試してみようと思い、インストールしてみました。

7つの言語 7つの世界7つの言語 7つの世界
(2011/07/23)
Bruce A. Tate

商品詳細を見る

ダウンロードとリンク

インストール用のバイナリーパッケージは、 Io Langeuage のサイトからダウンロードできます。 lang_io_dl.png


情報サイトもいくつか挙げておきます。

インストール

ダウンロードした圧縮ファイルは中にさらに自動解凍形式の圧縮ファイルが入っています。
その中のフォルダー構成は以下のようになっています。
IoLanguage/
  ├ bin/
  └ lib/
これを任意のフォルダーに展開します。
解凍後の bin フォルダーを 環境変数 PATH に追加するか、 フルパスで使用します。


私の場合は Unix 系のフォルダー構成のものは d:/usr にしているので、 IoLanguage 以下のフォルダーを d:/usr に移動して使っています。

使用法

bin フォルダー以下に io.exe があり、これを使用します。
使用法: io [-h | -e expr | -i file.io, file.io, ...| file.io arg, arg, ... | --version]

オプション:
  --version   バージョン情報の表示
  -h          ヘルプの表示
  -e          コードを引数として渡して実行
  -i          指定ファイルを読み取った後、対話モードを起動
Io はインタープリター型の言語です。コマンドにソースファイルを渡して実行します。
~/seven/io $ io hello.io foo bar
Hello Io
0:hello.io
1:foo
2:bar
ソースファイルを書くときに注意点があります。
コメントも含め、日本語を記述する場合は文字コードを UTF-8 にして下さい。 Shift-JIS だと固まる場合があります。

hello.io :
"Hello Io" println
# コマンドライン引数の出力
System args foreach(cnt, val, writeln(cnt, ":", val))
引数なしか -i の場合は対話モードで起動します。
eshell で動かないので個人的にはあまり使っていませんが、 ちょっと動作を試したいという時に使えるんじゃないかと思います。

lang_io_inter.png


-e でコードを渡すとそれを実行して終了します。
通常使わないと思いますが、 他のアプリとの連携などに向いています。

Emacs 用モードのインストール

Io 用の Emacs モードのインストール方法についても紹介します。

パッケージにはなっていないようなので、GitHub から io-mode.el ファイルをダウンロードします。 io-mode.el ファイルだけのダウンロードで大丈夫でしょう。
ダウンロードについては以前の記事をご覧下さい。 ダウンロードしたら、ファイルを load-path の通ったフォルダーに置きます。


~/emacs.d/init.el に以下の記述を追加して下さい。 拡張子が io のファイルを開くと io-mode となります。
(add-to-list 'auto-mode-alist '("\\.io\\'" . io-mode))
(autoload 'io-mode "io-mode" "Major mode to edit Io language files in Emacs." t)
私の環境では上手く動きませんでしたが、 C-c <SPC> などで emacs 上でのコードの評価もできるらしいです。



以上で設定は完了です。
ただ、使っていて気に入らなかった点が 2 点ほどありました。 私の場合はその修正もしています。

1 つ目はファイルを保存するとき、勝手に空白を削除する機能です。
私は結構まめに保存する方なので、インデントが消えてかなり邪魔です。 Emacs のカスタマイズで機能を無効にすることができます。
  • io ファイルを開いて io-mode
  • M-x customize-mode
  • [Io Cleanup Whitespace] の項目を [off] にして、設定(Set)、保存(Save)
2 つ目は [Enter] が "改行 + インデント" になっている点です。
しかもインデントがあまり賢くないので、インデントを戻したい時は削除していく必要があります。 ここで保存時のインデント削除が生きてくるのでしょうが、ちょっと特殊です。

[Enter] はただの改行で、 Ctrl+j が "改行 + インデント" という動作が標準的だと思います。
io-mode.el の 338 行目付近の "C-m" を "C-j" に変更すると、 キーバインドが修正されます。
;;;###autoload
(define-derived-mode io-mode fundamental-mode
  "Io"
  "Major mode for editing Io language..."

  (define-key io-mode-map (kbd "C-j") 'io-newline-and-indent)
  (define-key io-mode-map (kbd "C-c <SPC>") 'io-repl)
  (define-key io-mode-map (kbd "C-c C-c") 'io-repl-sbuffer)
 

Prolog のインストール(Windows) と Emacs の設定

今回は Prolog の Windows へのインストール方法と Emacs の prolog-mode 設定に関する記事です。

Prolog は論理型のプログラミング言語です。
論理型といえば Prolog という感じだったので、 プログラミングパラダイムの話などではよく目にしました。 しかし、実際どんな言語かはよく知りません。 『7つの言語 7つの世界』 に載っていたのを機に、ちょっと試してみようと思い、インストールしてみました。

7つの言語 7つの世界7つの言語 7つの世界
(2011/07/23)
Bruce A. Tate

商品詳細を見る

Prolog のインストール

処理系の選択

Prolog には多くの処理系があります。
Windows では SWI-PrologGNU Prolog あたりがメジャーなようです。
『7つの言語 7つの世界』 では GNU Prolog で説明されていましたが、 ともに ISO 準拠 なので、どちらでも構わないでしょう。


GNU Prolog では MinGW または MSVC++(Visual Studio) が必要みたいです。 SWI-Prolog はなくても使うことができます。
C コンパイラーは 実行ファイル(exe) を作成するのに使われます。 ドキュメントよれば、 SWI-Prolog はなくても動作しますが、 MinGW か VS を使って exe を作ることもできるらしいです。 SWI-Prolog の方が手軽に始められますし、本格的に使うようになったら、 C コンパイラーを使うことにすればいいので、 SWI-Prolog を使う ことにします。

ダウンロードとインストール

インストール用のバイナリーパッケージは以下のページから w32plXXX.exe (w64plXXX.exe) をダウンロードします。 lang_prolog_dl.png

ダウンロードしたインストーラーを実行して、ウィザードに従っていけば、インストールは完了します。

私の場合は一箇所変更したのは拡張子のところです。
lang_prolog_inst.png
Prolog では pl または pro がよく使われる拡張子のようです。
しかし、 pl は Perl の拡張子とかぶっています。 Prolog の方が歴史が古いので、 Prolog は悪くないのですが、ちょっと困ります。
pro の方も何かのプロジェクトファイルで使いそうな名前です。 実際、 Qt のプロジェクトファイルが pro です。
仕方がないので、 私は Prolog の拡張子を prl にしました。

使い方

Prolog は他のプログラミング言語と比べると使い方が特殊です。
大まかな使用手順は次のような感じになっています。
  1. Prolog を起動 (対話モードとなる)
  2. 知識ベース(事実とルール)を登録
  3. 質問をして答えをもらう

知識ベースファイル

知識ベースはファイルに記述し、読み込ませることが多いでしょう。 これが他の言語のソースファイルにあたります。

syllogism.prl :
% 三段論法

% 事実 : ソクラテスは人間である
human(socrates).
% ルール : 人間は死ぬ
die(X) :- human(X).
Prolog では名前の付け方でアトム(固有値)か変数を判断します。 サンプルでは socrates などがアトムで、 X が変数となります。

名前の付け方のルールは次のようになっています。
種別 ルール
アトム 小文字, ' で囲まれた文字 socrates, 'Socrates', 'ソクラテス'
変数 大文字, _ で始まる文字 X, _x, _何
例のように書けば、一応、日本語は使えます。
コメント以外で日本語を使う場合は BOM 付きの UTF-8 で保存します。

ただし、質問等の対話入力で表示がおかしくなったり、 後述する Emacs からの使用で正常に動作しなかったりします。

起動と知識ベースファイルの読み込み

実際に使う場合には、 (インストールフォルダー)/bin 以下にある次の 2 つのどちらかを使用します。
  • swipl-win.exe
  • swipl.exe
swipl.exe はコンソール版なので、 通常 swpl-win.exe を使います。
swipl.exe は Emacs との連携などで使用します。

[スタート] メニューから Prolog を起動すると swipl-win.exe が実行されます。
lang_prolog_exe.png

ここで、 メニューか、次のようなコマンドでファイルを読み込みます。
パス区切りは \\(2 回重ねる) または / です。
  ['ファイルパス'].
また、インストール時に関連付けた拡張子のファイルを実行すると、 Prolog の起動とそのファイルの読み込みが行われます。 おそらく、こちらの方が簡単でしょう。

質問

知識ベースのファイルを読み取った後、 対話的に質問やコマンドを入力します。
質問すると答えが返ってきて、次のプロンプトが出ます。
1 ?- die(socrates). % ソクラテスは死ぬ ? 
true.

2 ?- die(What).     % 何(What)が死ぬ ? 
What = socrates.

知識ベースを変えて、複数の答えが返ってくるようにしてみます。
human(socrates).
human(plato).
human(aristotle).
die(X) :- human(X).
答えが複数ある場合、 一つだけ答えを出して止まります。
ここで、 ; のキーを入力すると次の答えが出ます。
; により順に答えを出していき、すべての答えを出すと、次のプロンプトにいきます。
1 ?- die(What).
What = socrates ;
What = plato ;
What = aristotle.
; ではなく、 a を入力すると途中でやめて、すぐ次のプロンプトにいきます。
GNU Prolog では a で、 すべての答えを出してから、次のプロンプトです。 GNU Prolog と a の動作が違うので、注意して下さい。

他のキーは ?h を入力するとヘルプが表示されるので、そちらで確認してください。

終了

終了する場合にはメニューや [X] ボタンで終了するか、 halt. と入力します。

Emacs の設定

知識ベースのファイルを記述するための Emacs 用のモードのインストール方法について紹介します。

また、 Emacs から Prolog を使えるようにすれば、 ウィンドウを切り替える必要も無いですし、補完もできて非常に便利です。 その設定についても説明しておきます。

Emacs 用モードのインストール

Prolog 用のモード prolog-mode はパッケージが用意されているので、 簡単にインストールすることができます。

[Option] -> [Manage Emacs Packages] メニューでパッケージのモードを開き、 prolog のパッケージをインストールします。

パッケージをインストールすれば、 prolog-mode の auto-load の設定はされます。 ただし、 拡張子との対応付けまではしてくれないようです。
*.prl のファイルを開くと prolog-mode になるように ~/emacs.d/init.el に以下の記述を追加します。 拡張子の部分は自分が使っているものに変えておいてください。
;; Prolog
(add-to-list 'auto-mode-alist '("\\.prl$" . prolog-mode))

Emacs からの Prolog の実行

prolog-mode の設定をすれば、 Prolog を実行できるようになります。
  1. prolog-mode にする
  2. M-x customize-mode で prolog-mode の カスタマイズ画面を開く
  3. [Prolog System] の変数を SWI Prolog に設定
  4. Set, Save
lang_prolog_emacs_type.png

Prolog System の設定で他の変数も SWI Prolog 用のものが使われるようになります。 ただし、 Prolog の実行ファイルパスは指定しておく必要があります。
  1. [Prolog Inferor] を選択して、 ページを移動
  2. [Prolog Program Name] 変数の (swi "pl")の部分をインストール先の swipl.exe に変更
  3. Set, Save
lang_prolog_emacs_progpath.png




使う場合は、 C-c C-f で Prolog を起動し、対象ファイルを読み込みます。(consult)
起動だけの場合は C-c [Retun] です。
lang_prolog_exe_emacs.png

"1 ?- " といったプロンプトは表示されませんが、 なくても動作に問題はありません。

ファイル以外にも、バッファー、選択領域などを読み込ませることができます。
[Prolog] のメニューから操作することもできるので、キーバインドなどはそちらで確認して下さい。


 
このページをシェア
アクセスカウンター
アクセスランキング
[ジャンルランキング]
コンピュータ
26位
アクセスランキングを見る>>

[サブジャンルランキング]
プログラミング
8位
アクセスランキングを見る>>
カレンダー(アーカイブ)
プルダウン 降順 昇順 年別

05月 | 2023年06月 | 07月
- - - - 1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 -


はてな新着記事
はてな人気記事
ブロとも申請フォーム
プロフィール

yohshiy

Author:yohshiy
職業プログラマー。
仕事は主に C++ ですが、軽い言語マニアなので、色々使っています。

はてブ:yohshiy のブックマーク
Twitter:@yohshiy

サイト紹介
プログラミング好きのブログです。プログラミング関連の話題や公開ソフトの開発記などを雑多に書いてます。ただ、たまに英語やネット系の話になることも。