Mops Roundup

Mops概説(Mops Roundup): The Mops Programming Language


この記事は、とても役に立つ事柄を含んでいますが、Ed Williams氏が書かれたもので、もともとはe-zine ATPM -- About This Particular Macintoshに二部構成(Part1
およびPart2)で掲載されたものです(素材は少し変更されています)。この記事は、Mops/PowerMopsの初心者ユーザーまたはこれから利用するかもしれない人に向けて書かれています。

プログラミング言語 Mops


はじめに

パーソナルコンピューティング経験のなかでも実りが多いのは、役に立つものであれ何であれ、自分のプログラムを書くことです。これは少し危険なことです----常習者、あるいは少なくとも癖になってしまうかもしれないからです。いずれにせよ、少しはMacでのプログラミングに関心があるというのであれば、ぴったりのフリーウェアプログラミングシステムがあります。その名はMops。それは、Macintoshだけのためにデザインされているのです。Mopsシステムは、飽くことを知らない有能なオーストラリア人プログラマであるMike Horeによって、過去15年以上に渡って、暇をみつけては開発され続けてきました。現在配布されているシステムのサイズからいえば、Mopsはまさに最高のMacソフトウェアとしての価値をもつといえるでしょう(7.4MBを$0で割ってみれば判ります----いえいえ、これは実行できません。)

Mops, ForthそしてOOP

Mopsの非凡さは、デザイン上本質的にMacintosh用プログラミング言語になっているという点にあります。それはAppleScriptが本質的にMacintosh用スクリプト言語であるのに匹敵します。この事実は、それらのどちらにおいても(比較的)容易に利用できるという性質をもたらしています。----もっとも、プログラミングは決して容易なわざではありませんが。そのうえ、Mopsは古いものと新しいものの最良の部分を継ぎ目なく混ぜ合わせているという点でユニークです。その言語は、一般に利用されている最古のプログラミング言語に属するForthに基づいています。MopsはそのForthバックボーンに、非常に堅牢で完全な形のオブジェクト指向プログラミング(OOP)を付け加えました。MopsのOOP実装はOOPシステムが提供すべきものとされるほとんど全ての事柄を含んでいます。多重継承さえ備えているのです。

MopsはMacintosh専用ですから、必要なMacツールボックスおよびシステムサービスを提供するために別段のAPIやフレームワークは必要ありません。ある意味、それらは組込まれています。ですから、どんな種類のMacプログラムも作れますし、(コールバックルーチンなどを含んでいる)シェアードライブラリ(これはシステム拡張を速やかに駆逐しつつあります)も構築することができます。G4 MacintoshモデルのAltiVec実数コプロセッサも、ウィザード水準のベクトル計算プロセッシングのためにサポートされています。(PhotoshopがG4ではなぜこんなに速く走るのか不思議に思ったことはありませんか?)

OSXより前のMacのプログラミングサービスをまだあまり御存じない方へ:ツールボックスというのは、OSが提供する途方もなく多くのサービス---- 明らかなものをいくつかあげれば、ウィンドウ、メニュー、ダイアログ ---- への、伝統的なプログラミングインターフェイスを提供するものです。ツールボックスは、数多く定義された"Syscall"の一つによって呼び出され、実行時にはシステムが割り込みをします。最近のOSの幾つかのバージョンでは、CarbonLibというシェアードライブラリが、"カーボン化"されたプログラム---- MacOS9とMacOSXで両立できるようにつくられたもの ---- については、これらの呼び出しの大部分を受け持ち、両プラットフォーム上で、同じ、あるいは類似のサービスを提供することを目指しています。

Forth : その歴史

Forthという言語はいくつかの歴史的背景をもっています。Charles H. Mooreは独力でForthを発明したという栄誉が与えられるべき人です。;彼のアイディアが形を取るのは、1950年代のスミソニアン天文研究所でのことです。そこで彼は、その言語がどうあるべきかについて、いくつかの鍵を見つけました。Mooreは1969年までにはForth言語の開発を完成しており、その名前で呼んでいました。初めはIBM 1130ミニコンピュータ用でした。Mooreは、なかでもコンピュータグラフィックスプログラムをForthで書きました。マイクロプロセッサの発明に伴って、Forthは埋込み型コントロールプロセッサでは当たり前になりました。この種のデバイスではコントロールソフトウェアのための領域はごく小さなものです。巨大なプログラミング開発システムなどお話にもなりません。Forthはクロスコンパイリングに役立ち、小さなマシン語プログラムを生産しました。

ANSI Forthスタンダードは存在してはいますが、実際のForthの世界は、おたがいにも、そして標準からも少しずつ異なる、たくさんの変種によって特徴付けられます。ですから、確かにMopsは十分にForthの一員としての地位を確立してはいますが、そのコードのバックボーンは(Forthそのものというよりも)Forth風というのが合理的でしょう。Mopsは実際、特に、Macintoshプログラミングのため、便宜のため、そしてオブジェクト指向オペレーションのために、Forthの規格にはないForth風のwordの定義を数多く追加しています。(Mopsは容易に標準にしたがったANSI Forthコンパイラに変換できます。そして、同じくらい容易に元に戻すことができます。)

Mopsは高水準のOOP構成に服する優れたForth風言語ということができます。OOPの上着を着たForthと言うこともできるかも知れません(が、そういうべきではないでしょう)。コードは、他の全てのForthベースの言語と同様、三つの主要な特徴で性格付けられます。:顕在的なスタック、演算子後置(Postfix)記法、そして、言語の拡張性です。

Forthが進化していた時代には、プログラムが利用可能なメモリーは、現在の水準から見れば僅少なものでした。したがって、Forthの記号法の風変わりな性格は、その発明者の恣意や目新しさのために採用されたものではありません。メモリーが安価で豊富となった今となっても、コンパクトなコードが生成されるということは、その実行速度のためには、なお当時に劣らぬ価値があります。

Mops V.4は、Mopsページからダウンロードすることができます。このウェブサイトはたくさんのその他の有用で入門的なアイテムを含んでおり、Forthのドキュメンテーションおよびリソースへのリンクがあります。この中には、この記事の後の方で引用する『Forthの進化(The Evolution of Forth)』も含まれています。

現時点では、PowerMopsのカーボン化されたバージョンV. 5.xが、OSXとの互換性を目指して、利用可能となっています。"古い"標準的なMopsは、Macintosh plusにまで遡る68kプロセッサのためのプログラムを生成します。(‘legacy’コンパイラというべきでしょうか。)より新しいPowerMopsはPowerPCマシンにネイティブにコンパイルし、FATアプリケーション(68kおよびPowerPCマシン両用)もビルドすることができます。

露出スタックと"うしろ向き"記号法

コンパイラの大部分は内部的にスタックオペレーションを広範に利用しますが、効率化のため、Forthではひとつのスタックがいわばオープンにされており、プログラマーがコントロールできるようになっています。これは、データスタック、またはパラメタースタックと呼ばれています。それは、ひとつのForthワードないし手続から、別のそれにデータを渡すために用います。

第二のスタックであるリターンスタックは、大抵は、データではなくて、プログラムのリターンアドレスを含んでいます。しかし、習熟したForthプログラマーなら、それを(一時的にデータを保管するために)利用することも可能です。無限定に"スタック"というときには、いつもデータスタックを意味します。これは、この言語の性質上、プログラマーによって管理される必要があります。(アセンブリ言語のプログラマーも、一般に、これもまた効率化のために、コード内で一つまたはそれより多いスタックを採用します。)

Mops --- そしてその他のForthベースの言語でも同様---は、普通の押し下げスタック(push-down stack)を用います。これは後入先出(LIFO)リストともいわれます。Mopsのコーディングスタイルと‘文法’、つまり、正しく言語を拡張する方法は、LIFOリスト操作の表現が適切であるかどうかにかかってきます。

ForthのエキスパートであるElizabeth Ratherは、Association for Computer Machineryに提出された彼女のペーパー「Forthの進化」の中で簡潔に述べています:
Forthの明示的なスタック使用は、被演算項が演算子に先立つPostfix記法を導く。演算の結果がスタックに残されるため、複数の演算を無理なく連結することができる。そして、一時的データ保管のために変数を定義する必要はほとんど生じない。
(このペーパーは、Ms. Ratherが、常にMooreと"同室にある"か、さもなければ"隣室にある"かのいずれかであった人であるという点で、歴史的に非常に注目すべきものです。彼女自身もこの言語に影響を及ぼしました。)彼女がここで述べているのは、スタックプログラミングはpostfix記法/逆ポーランド記法(RPN)の利用へと、作業への適合性ゆえに自然に繋がるということであり、スタックを利用すれば、変数宣言が必要になる場面は劇的に減少するということだと思います。実際、ヒューレット-パッカードの携帯電算機もRPNを利用してますが、----御想像の通り----スタック指向でもあります。

図1は、一つのスタックの三つの継続する‘ライフ’の段階ないし時点を記述したものです。TOSはスタック最上位(Top Of Stack)を表わしています。

スタック模式図
図1: スタックの三つの段階

次は、全く単純なMopsワードの定義です。これをADDと名付けます。理由は明らかでしょう。:
: ADD   ( n1 n2 -- sum )  +    ;

ワードAddがすることは、それが呼び出されたときにスタックにおかれているはずの二つの数値を足しあわせることだけです。このワードのスタックコメント(括弧の中)にある記号n1とn2がスタック上に予定される数値を示しています。記号‘sum’は、このワードを呼び出したルーチンに戻ったときには、それはスタックのトップに(この算術計算の性質に従った)結果を残すだろうということを示しています。この定義にある唯一つのコードは、一つの加法演算子ないしプラス記号であることに注意しましょう。

さて、スタックの図に戻りましょう。;一番左の絵は、Addを呼び出す直前のスタックの状態を表わしています。スタックの内容は、入れ子状になった(一部分だけ実行された)いくつかのワードが利用することになっている値と、ツールボックスルーチンによってスタック上に押し込まれた情報といったところです。中間の絵は、ワードAddを呼び出す側によってスタックに押し込まれた二つの整数を表わしています。これらはワードAddの呼出し専用に押し込まれたものです。

右側の絵は演算の結果がトップスタック上におかれることを表わしています。二つのことに注意しましょう。:ひとつには、Mopsの演算子は、その被演算項を一様に消費する、というか使い切ることであり、ふたつめは、スタックの最上位(TOS)のポインタはMopsが自動で面倒を見てくれるということです。(一般に、被演算項がスタック上に残ることは決してありませんが、中間的結果やもっと複雑な処理過程は残ります。)

下は、もう少し面白いMopsのコードサンプルです。

Value  TEMP  ( at top level )

: SUM-OF-SQUARES  ( n1 n2 -- n3 )
  DUP * -> Temp    DUP * Temp +  ;

Value宣言はTempという名前の大域変数を作り出します。‘:’(コロン)は新たな語を定義するためのForthワードです。コロンからワードの定義が始まり、その後にはその新ワードの名前がきます。この場合には、SUM-OF-SQUARESです。(二乗和演算(sum of squares)とは、二つかそれ以上の個数の数の二乗をつくり、それらを足しあわせるものです。)括弧内に書かれたコメントは、スタック効果コメント、ときには、スタック記録と呼ばれます。スタックコメントはオプションであって、純粋に記録のためのものでしかありませんが、Forthプログラミングでは非常に重要なものとみなされています。コメントを利用すれば、定義されたワードが、実行される際に入力としてスタック上に何を必要とし、出力としてスタック上に何を残すのか、を明確にすることができます。スタックコメントが非常に複雑になることもあり得ますが、そんなときこそ、まさにコメントが最も必要なときと言えるでしょう。

この例でも、このワードが呼ばれる際にスタックの上位に二つの整数があることが必要です。これはn1とn2が表わしています。呼び出し側のワードと呼び出されるワードとの間でこの点に関して齟齬があった場合には、コンピュータ世界は大混乱に陥ります。呼び出されたワードが、それに属していないスタック上のデータを‘喰って’しまったなら、スタックはすぐに辻褄が合わなくなるでしょう。(n2、というより入力パラメターの一番右に書かれた記号はいつもスタックの最上位の値を表わしていますが、このことはこの場合には重要ではありません。)記号n3は、ここで定義されているワードが、終了したときにはスタック上にひとつの値を押し込むだろうということを示しています(性質上、計算された自乗和がそれです)。

スタックコメントは決してデータスタックの地図ではない、ということに注意して下さい。スタック上には、与えられたワードの実行に関連する値より下の方にも、たくさんの値が存在しているかも知れません。下の方のデータアイテムは、それを消費するであろう別のワードに属するものです。

ワードDUPは、スタック操作専用のもので、n2で表わされているトップスタックの値をコピーして、新たなトップスタックセルを作り出します。積演算子‘*’は、トップスタックの値とその直下の値とを掛け算します。その際には、それら二つの値を費消して、積の値が入った新たなトップスタックセルを作り出します。Mops特有の演算子である‘->’(‘中に入れる(gazinta)’というひらめきに溢れた名前で呼ばれていますが)は、トップスタックの値を、この例のTempのような、ある種の変数に割り付ける格納演算子です。したがって、n2の二乗はスタックから取り除かれ、今度はn1がトップになります。(もちろん、ここでn1,n2といっているのはそれで表わされているデータの値のことです。)

コードの2行目は、n1で表わされる値の二乗を前と同じようにして作り出しますが、続いて、単にその名前を書くことを通じてTempの値をスタック上に押し込み、スタックの最上位の値(n2の二乗)とスタックの上から二番目の値(n1の二乗)を足しあわせます。これらの被加算項は、どちらも加法演算によって費消され、その和が、このワードの戻り値としてスタックに押込まれます。(ワードSUM-OF-SQUARESは、いくつかの他の言語では関数と考えるのが適切なものでしょう。)スタックコメントは、文字どおりには普通の意味のプログラムコメントとは別個のものであるとしても、その有用性ははっきりしたものであると思います。

ワード‘;’ (セミコロン)は、ワードの定義を終了させるものです。この短い2行のコードの中で、随分多くのことが起こっていると思いませんか?同じルーチンの、変数の宣言を必要としない、もっと巧妙で本来的なバージョンは、次のようになります。:
: SUM-OF-SQUARES  ( n1 n2 -- n3 )
  DUP * SWAP DUP * + ;
非常に便利なワードSWAPは、これも専用のスタック操作子ですが、上ふたつのスタックセルの値を取り替えます。何も費消しません。これだけたくさん説明してきたのですから、残りは研鑽を積まんとする皆さんへの演習として残します。それぞれのワードひとつひとつに集中しましょう。まったく簡単です。

名前付きの入力パラメターや局所変数を用いて、目に見える形でのスタック操作を大部分隠してしまったり、制限したりする方法も、プログラマーにはいくつか利用できます。時として、あまりに複雑な操作が必要になってしまって、それらのような魔法の変数を用いる必要が生じ得ます。(名前付き入力パラメターと局所変数は、Mops特有の言語機能です。)

Mopsのスタック指向がもたらす、プログラマにとっての直接の利点は:
  1. 速くてコンパクトなコードが生成できる。(Mopsプログラムは、ときとしてアセンブラコードに比肩しうるものとなります。)
  2. 大域変数を大きく減らすことができる、というのは、入力・出力パラメターはどちらもスタック上におくことができ、スタックを直前の結果を保存するために利用することができるからです。これは、プログラマーにとっては間違いを起こし易い面倒な作業を省き、プログラムに必要なメモリも減らすことになります。
  3. Mopsの手続は外部変数を使わない限り本来的に自己呼出し可能(Re-entrant)であって、これはコードを単純化するのに決定的に有利です。(再帰性(Re-Entrancy)とは手続が実行中にそれ自身を呼び出しうる能力のことで、非常に便利なものであり、ある種のアプリケーションでは不可欠なものでもあります。)大部分の言語が何らかの再帰性を提供していますが、特別な追加的コンパイラコードを用いて行っています。これに対してMopsの手続は単純かつ生来的に再帰的です。
  4. 通常の高水準Mopsワードだけを使ったMopsプログラムは、生来的に(Dijkstra/Parnasの定義の意味で)構造化・モジュール化されています。EXITとEXECUTEを使った場合だけは例外となります(これらはプログラムコントロールを逸らすものです)。プログラミング理論においては、構造化され、かつ/またはモジュール化されたプログラムは信頼性が高いとされています。私の解釈では、これは隠れた‘しくじり(gotchas)’を含みにくいということになります。

ワード、ワード、そしてワード

Mopsが持つForth的単純性のもうひとつの側面として、成語規則(どうすればその言語の有効な基礎的要素を形成できるかを教える規則)が非常に単純であることがあります。Forthにおけるシンタックス的要素、ないし識別子とは、空白文字で区切られた文字列である、おしまい。この文字列には空白文字(空白、タブ、改行)を除けば、どんな文字を混ぜてもかまいません。この文字列は、好きなだけ長くても短くてもかまいません。

Mopsの文法は、上で見たような(空白で区切られた:と;)新たなワードを定義するための単純な記号と、その他のいくつかの同じように単純な記号とから成っています。もちろん、Mopsにはたくさんの異なる種類のワードがありますが、成語的にはそれらはすべて同じものです。OOP構成にはいくつか追加的な規則が必要になりますが、それらも非常に少なく、かつ単純です。実際、Forthはシンタックスを必要としていないため、それを持たない、という人もいるくらいです。シンタックス上の単純さによって、この言語の三番目の主要な特徴がもたらされます。:利用者による拡張可能性です。

通常の(高水準の)Mopsワードは次の三つのカテゴリーに分類されます。:名前付きデータアイテム、名前付き手続、定義用ワード。定義用ワードにより、利用者は名前付き手続、つまり新たなMopsワードとアクションワードと呼ばれるものを定義できるようになります。実行可能ワード、あるいは‘被定義ワード’は、機能的には他の言語におけるルーチン・サブルーチン・関数に類似し、場合によっては他の言語のコマンドに相当するものもあります。例えば代数がFortranのメタファーないしモデルであるのと同様、Forthのメタファーは自然言語の散文です(‘動詞’が最後にくるドイツ語であるという人もいます。)。非常に多様な範囲に渡る機能的にも様々な要素を、任意の文字列で表わすことができます。予約語は実際上存在しません(:と;が数少ない例外に属します)。辞書中のほとんどどんな語でも、その名前を再定義することができます。

Forth言語系でワードという言葉が強調される源泉のひとつは、辞書という機構が用いられいることにもあります。例えば‘+’のようなMopsの既定ワードの全て、そして---ある時点では---利用者のプログラムを包括する全ワードが、辞書内に存在しています。大まかにいえば、まさに辞書こそが、利用者のプログラムそのものなのです。(その辞書の解釈は、小さくて迅速なマシンインストラクションの仕事です。)概念的には、辞書の下に、コンパイラ/インタープリタ プリミティブを含む小さな核が存在しています。

これらの成語的考察全体から導かれるのは、利用者が既存のワードを組合わせて自由に新たなワードを作り出すことができる、完全拡張可能言語です。このことによって、プログラムを容易に整理整頓(factor)することができるようになります--これは非常に重要なプログラミングテクニックです--。

ベテランであるPhillip J. Koopmanは次のように述べています。:
Forthプログラムを書くということは、その言語を拡張して、あるアプリケーションの実装に必要な関数の全てを包含するようにする、ということと同値である。したがって、Forthでプログラミングするということは、アプリケーション専用の言語拡張を作り出すことと考えることができる。このパラダイムは、非常に速い編集/コンパイル/テスト周期と相俟って、生産性を著しく増大させるものであるように思われる。各Forthワードが書かれる毎に、それをキーボードからテストして、直ちにプログラマーにフィードバックすることができるのである。

言語の拡張は、プログラムのためにコンパイルされるコードに反映されます。辞書は、はじめにはMops言語の定義しか含んでいませんが、利用者プログラムでの定義を全て含むように、文字通り拡張されます。プログラムがスタンドアローン、つまりダブルクリックで起動可能なプログラムとしてインストールされる前であれ後であれ、そのプログラムの実行とは、結局のところ、辞書を解釈することに帰するのです。

Mopsプログラムの解釈


Mopsプログラムがスタンドアローンで実行されるようにコンパイルされたときでも、プログラムはなおインタープリタモードで実行される、という見方について、補強しておいた方がいいかも知れません。このような実行モードは、しばしば貧弱なパフォーマンスと結びつきますが、Mopsプログラムに関してはそうではありません。

Postfixの解釈可能言語としては、Mopsは、CやJAVAやPascalのような他の今日のポピュラーなプログラミング言語よりも、PostScriptとより多くの共通点を持っています(構造化コードはPascalと共通ですが)。他方、AppleScriptはインタープリタ言語ですが、‘超高速化’よりも使い易さに重点をおいて設計されています。

例えば、上で定義したワードSUM-OF-SQUARESの定義を、何かのプログラムソースに組込む前に、Mopsのデータ入力ウィンドウに入力して呼び出すことによって、インタラクティブ解釈モードでテストすることができます。とても親しみやすいものです。下の図2のように、SUM-OF-SQUARESの定義をMopsのデータ入力(および編集)ウィンドウに入力しました。横線から下の部分です。

定義部分を選択してEnter(リターンでなく)キーを押します。しかしそれをした後も、図のウィンドウの上側部分に見られるように、スタックは空っぽ(empty)なままです。 Mops2 これは、SUM-OF-SQUARESの定義がコンパイルされてMopsワードとして辞書に移されたからです。(ワードとしての‘コロン’「:」によってMopsはコンパイルモードに入ります。)

図2: ワード定義の入力

図3では、12と24の二つの値が入力され、SUM-OF-SQUARESが単純に名前を書くことによって呼び出されています(キー入力の後Enterキーを押します)。Mopsは、解釈モードでは、数値をスタック上に置き(標準的な動作です)、名前に適合するワードを辞書内に探します--この場合には、そう遠くまで探すまでもありません--。そして、見つかったなら、適合するワードが実行されます。最後には、sum-of-squaresの結果をスタック上に見ることになるのです。

もしもエイリアンのような高速視力を持っていたなら、ワードSUM-OF-SQUARESによって喰われてしまう前の二つの数値がスタック上に見えたことでしょう。もちろん、スタック上の値それ自体をまずenterして、SUM-OF-SQUARESを起動する前に見てみることもできます。 Mops上でテスト もっと複雑なワード定義を辞書に読み込んだときには、キーボードの前に座って、入力の値の組合わせを様々に変えて、日がな一日テストを続けることもできます。

図3: テストのために値を入力する


プログラムの小さな断片部分を他の大部分から独立にテストすることができるということの利点は明瞭でしょう。

Mopsのオブジェクト指向

さて、Mopsのユニークなオブジェクト指向特性に話を遷しましょう。この特徴を備えたフリーウェア言語は、私の知る限りでは、他にはありません。Mopsの唯一人の領主であるMike Horeによれば、オブジェクト指向プログラミング(OOP)は"この言語の本当の力の源泉"であり、付け加えさせてもらえば、それはその主要な"ウリ"です。オブジェクト指向は、完全な形であれ部分的なものであれ、今日ではとてもポピュラーなので、この記事でその利点と美点を売り込む必要はないでしょう。

続く3つの節では、OOPの背景を成す考え方を、いくぶん抽象的に説明しています。OOPの概念をもう良く知っているのなら、"クラス宣言の実例"まで跳んでもかまいません。概念の説明は、Xerox PARCのメンバーでSmalltalkを開発した、Adele Goldbergの見方に従います。彼女は、OOPの発明者と呼ばれるに相応しい人です。ともかく彼女は、確かにかつてPARCスタッフの一員であり、Alan Keyの印象的な言葉によれば、"両手に稲妻をもあやつる"訳者註人でした。

訳者註: 訳に自信がありません。原文は"dealt lightning with both hands"です。

目的

OOPの主要な目的は、プログラムの複雑さをより上手く管理することです。OOP実装を行うならば、最初の段階でのプログラムないしシステムの信頼性を向上させるだけではなく、同じくらい重要なことですが、コードの保守管理可能性を拡大させます。

モデル

OOPのモデルは、独立した、互いにコミュニケートするオブジェクトというモデルです。このモデルによれば、あるオブジェクトは、もうひとつのオブジェクトにメッセージを送り、それを受け取ったオブジェクトだけが扱い方を知っている、ある操作を要求します。この操作がどのように実行されるかを決めるのはメッセージを受け取ったオブジェクトだけです。したがって、"コンピューティングは、オブジェクトに内在する能力とみなされ、これはいつもメッセージを送ることによって起動される"(A. Goldberg)ことになります。

上に引用された内容からは、Forth、Modula、Algol、およびPascalといった言語が提供するものよりも高い水準のプログラミングパラダイムが導かれます。---これがもっと低水準のパラダイムに無理なく馴染ませられるものであることは、MopsにおいてForthとOOPが継ぎ目なく結合されていることが証拠になります。

基礎的概念

オブジェクト指向アプローチが要求するのは、たった5つの十分に定義された諸概念だけです。:オブジェクト、メッセージ、クラス、メソッド、インスタンスです。

実践上の区別からはじめましょう。:クラスは、ランタイムエンティティーであるオブジェクトを作り出すソースプログラムエンティティーと考えることができます。クラス定義は、オブジェクトのクラスを記述します。ひとつのクラス定義から、ひとつ、ないし複数の同等なオブジェクトを作り出すことができますが、それらオブジェクトのデータの値は、実行時には通常は互いに異なります。辞書内にコンパイルされた静的オブジェクトは固有名を持たなければなりません。個々のオブジェクトは、そのクラスのインスタンスと呼ばれます。

ひとつのオブジェクトは、データのために確保されたある量のメモリーと、ひと組のオペレーションないしメソッドから成ります。データとオペレーションの性質は、そのオブジェクトが表現しているものに依存します。それは、基礎的なデータオブジェクトから、スクリーン上のウィンドウ、ひいては、問題空間におけるあるエンティティーの複雑なモデルに至るまで、広がりうるものです。(例えば、Mopsのデモプログラムは、一群の数学表現を象った、眼を見張るようなパターンを造り出します。)オブジェクトのデータは、論理的にはそのメソッド、つまりそのデータ上に必要なオペレーションを実装するコードとパッケージ化されます。これは、論理的データ-コードカプセル化として知られています。

もうひとつのオブジェクトが、関連するデータを持っているオブジェクトにメッセージを送ることによって、特定のデータを保存、取り出し、加工などすることを求めることができます。この文脈では後者が受け手と呼ばれます。受け手は、そのメソッドセット中に対応物があるメッセージだけを認識します。オブジェクトの肝要な性質は、それ自身のメソッドによってしか、そのメモリーにアクセスすることはできない、ということです。メッセージの肝要な性質は、あるオブジェクトのメソッドを起動する唯一の方法であるということです。

これらの二つの性質の組合わせにより、ひとつのオブジェクトの実装は他のどのオブジェクトの実装のどんな特性にも依存し得ない、ということが保証されます。これは基礎的な利益であるばかりではなく、プログラム開発中、およびとりわけプログラムの保守管理において、実際的な利益にもなります。小さなプログラムを開発していたとしても、あるオブジェクトのクラスの実装を他のものに影響を与えることなく変えてみることができるということはうれしいものです。

Mopsのオブジェクトは一般に、そのデータ構造を構成し暗黙裡にメソッドを補助する、ひとつあるいは多くのもっと下位のレベルのオブジェクトを含んでいるという意味で、合成構造となっていることに注意しましょう。ですから、OOP規則にしたがって、合成されたオブジェクトは、それが含んでいるオブジェクトに‘内部的な’メッセージを送ることができますし、実際に常にそれを行っています。この内部的なメッセージは数多くのレベルで起こり得ます。したがって、ハイレベルのオブジェクトのメソッドは、そのオブジェクト自体ばかりでなく、それ以外の数多くのオブジェクトからも流れ出ている、オペレーションの滝と考えることができます。このことは、インフォーマルな書き方ではありますが、非常に重要なポイントです。

内部的メッセージを送ることは、最終的にはマシンコードに近い原始的なオペレーションで定式化されたメソッドに行き着く連鎖、と考えることができます。内部的メッセージは継承と相俟って非常に高次元の情報隠匿を提供します。プログラマーは、そのプログラムによって起動されるメソッドの大部分について、定義を見ることはありません。;採用しようとするメソッドの名前を知り、理解するだけでいいのです。

最後に、Mopsでは、メッセージは、それ自体はオブジェクトの一部ではないコード、つまり通常のMopsワードから送ることもできます。

クラス宣言の実例

Tic Tac Toeゲーム(三目並べ)を書いていると仮定しましょう。スクリーン上に起こっていることを継起的に記録する、ある種の‘雑用係’オブジェクトが必要であると判断したとしましょう。そのオブジェクトは、プレイフィールドをモデリングするデータレコードを必要とします。各マスに、“誰がそこに打ったか”コード(-1,0,1のどれか)で、印をつけることができなくてはなりません。さらに、そのオブジェクトは、高水準のゲーム遂行ないし決定コードに必要な情報を提供できる必要があります。そのような情報の例としては、‘ボード上のあるパス(行、列、対角線)にまだ誰も打っていないスペースがあるかどうか”や“ボード上のあるパスの数値的状態(-3から3まで)は何か”があります。

下は実際のゲームの実装に使われたクラス定義を略記したものです。(スタック記録の記号‘--’は入力と出力を仕分ける記号です。ですから、スタック記録‘( -- )’は、そのワードが、スタック上にはじめには何の値も期待せず、おわりには何の値も押込まない、というか生産しないということを表わしています。)

三目並べコードサンプル


(コードの)2行目が非常に重要です。そこではサイズが9のデータオブジェクトとしてwArrayクラスのデータ構造が宣言されています。このクラスは改めて定義する必要はありません。というのは、この定義は、数多くある他の既定クラスと同様、既に存在しているので、オブジェクトを宣言するだけでいいからです。(後で見るように、そのクラスに特有のメソッドも一緒に‘タダで’付いてきます。)したがって、我々のインスタンス変数、つまりivarは、9-セルのインデクス付きwordアレイです。

最初のメソッドの定義は、‘clearX: board_Arr’によってそれ自身を定義しており、これは実際には内部的なデータオブジェクトであるboard_Arrへのメッセージであり、それに対するメソッドclearX:は既に定義されています。(‘clearX’の‘X’はTic tac toeのXプレイヤーとは何の関係もありません。;それは作り付けのMopsメソッドの名前の一部でしかありません。)

メソッド名はコロン(:)で終わらなければならないことに注意して下さい。これは、ほとんど唯一といっていいくらい数少ないMopsの成語規則のひとつです。

次のメソッドFINDZCELL:の定義は、‘コード’とboard_Arrへの内部的メッセージとの結合を示しています。そのクラスのオブジェクトは、メソッド‘at:’をどうやって実行するかを知っています。これは、DOループによって提供されたインデックス値を入力としてうけ取ります。

メソッドPUTOMK:は、コンピュータが動いたときに利用されますが、スタックから何も受け取らない一方で、O-markコードの値と(大域)セルロケーションであるcelLocをスタック上に押込み、メソッドto:と結び付けているという点が、少し面白いでしょう。全てのwArrayクラスオブジェクトが知っているメソッドto:には、これら二つの値が必要なのです。;つまり、格納されるべきものとそれが格納される場所です。PUTXMK:はプレイヤーがマスをクリックしたときに呼び出されます。(プレイフィールドを受け持つ別のオブジェクトが対応するXおよびOをスクリーン上に書き込みます。)

ワード‘;CLASS’は定義を終了させます(‘;’がワード定義を終了させ、‘;m’がメソッド定義を終了させるのと同じです)。したがって、この例の最後の行は、定義の一部では全くなく、BoardRecordという名前のscorekeeperクラスのオブジェクトインスタンスの宣言のサンプルです。そのように、つまり宣言によって生成されたオブジェクトは静的オブジェクトであり、これを含むプログラムのMops辞書の中にコンパイルされます。(Mopsプログラム内のオブジェクトの大部分、しばしばその全てが、静的オブジェクトです。動的オブジェクトは、もう少し手間がかかります。)

Mopsの既定クラス

Mopsはたくさんの既定クラスを提供しています。これらは、単純なbyte/wordデータオブジェクトからMacintoshウィンドウ、メニューバー、ないしダイアローグに至る範囲のオブジェクトを定義しています。プログラマーとしてはもちろん後半の方の類のオブジェクトに主要な関心があるでしょう。というのは、大部分のプログラムは、ひとつないし複数のウィンドウ、メニュー、ダイアローグを必要とするからです。;そしてそれらを一からプログラムするのは、いわば、‘おぞましい’ことだからです。例えば、直接にMacツールボックスを用いてウィンドウをつくるのは、実際、悩ましい、間違いを犯し易い仕事です。さらに悪いことには、それにはかなりのMac内部に関する知識も必要で、これは、もし避けられるのであれば、大部分の人にはどうでも良いことです。

グラフィカルユーザーインターフェイス(GUI)オブジェクトに関するMopsの既定クラスは、OOPの継承メカニズムを用いて、そこで必要とされる汚らしい低レベルコードを隠しています。例えば、一般に用いられるWINDOW+クラスは、それが必要とする複雑なデータ構造やメソッドの一部の定義を、多くの他のもっと単純なクラスに帰着させているということができます。つまりWINDOW+クラスのインスタンスは、高度に合成的なオブジェクトとなっています。プログラマーはしかし、全く単純なWINDOW+の下位クラス(これはWINDOW+クラスの全てのデータとメソッドを継承します)を書くことで、上位クラスのいくつかの機能を上書きし、あるいは変形することもできます。

しかしながら、例えばWINDOW+クラスに属するオブジェクトを単純に宣言して、例えばそれがツールボックスにおいて‘生きている’ようにするメッセージなど、いくつかのメッセージを送るだけでよいこともしばしばです。既定オブジェクトのためのクラスの定義はソースプログラム中に書き込む必要はありません。というのは、問題の定義は普通はソースプログラムが読み込まれる前に、見えない形で予めロードされているからです。(そのいくつかは辞書のコアグループに属し、いつでもそこに存在しています。)しかし、プログラマーは、自分が用いるオブジェクトに提供されている機能を理解し適正なメッセージを送ることができるためには、そのオブジェクトが該当する既定クラスの定義を熟知していなければなりません。

ドラッグ、拡大、アップデートといった標準的なMacintoshウィンドウの動きは、Mopsのウィンドウクラスが自動的に処理するので、プログラマーは、システムレベルのコードをひっきりなしに書き直すことから解放され、アプリケーションレベルの問題に集中することができます。Mopsのツールボックスクラスの全てについて、ある程度までは同じことがいえます。

MopsはViewオブジェクトもサポートしており、WINDOW+クラスとあわせて広く用いられます。ビューは基本的には、描画を行うことができるウィンドウ内の長方形領域を定義します。ビューは‘子供’ビュー(サブビュー)を持つことができる他、様々な面白い性質を持っています。ビュー構成は、MacApp, TCL, PowerPlantあるいはCocoaといった大部分のMacintosh APIやフレームワークでもサポートされています。

では、一般にオブジェクトはどのようにして生成されるのでしょうか?既に見たように、全てのオブジェクトは、ソースプログラムにおいてクラス定義で定義された、オブジェクトのクラスの1インスタンスです。あるオブジェクトが静的に設置されるのが適当な場合には、上で見た(BOARDRECORD)ような単純なオブジェクト宣言でそれを作り出すことができます。この宣言は、そのクラスのオブジェクトのインスタンス化といわれています。静的オブジェクトの利点は、(動的オブジェクトに比べれば)全てが単純でトラブルが無いことです。また、メッセージ対オブジェクト結合がいつも静的(早期)結合(速い)であることもあげられます。潜在的な欠点は、辞書内にコンパイルされ(‘固着され’)るので、もう邪魔ものになってしまった後でも、プログラム空間を占拠し続けるということです -- これは大きなプログラムでは問題となり得ます --。

その代替物が動的オブジェクトです。これは実行時(runtime)にプログラムのヒープ上に生成されます。その際の構図は少し込み入っています。PowerMopsの最近のバージョン(V.4.x以上)では、動的オブジェクトを生成し管理するための非常に単純なメカニズムである‘Reference’が提供されていて、大部分の交渉を障害なく操作できるようになっています。他方Mops(68k)では、将来のオブジェクトのためにツールボックスからオブジェクトハンドルを得(objHandle宣言)て、オブジェクト生成ステートメント(handleオブジェクトへのnewobj:メッセージ)を送らなければなりません。そのようにして生成されたオブジェクトは、そのハンドル名またはインデックスによって参照されます。後者の‘伝統的’方法による場合には、動的オブジェクトに通常割り当てられるヒープのリロケータブルブロック管理に多大な注意を払わなければなりません。

Mops既定クラスの例

下は、基本的なデータクラス定義の例で、クラス名がVarの32ビット整数変数のためのものです。しかし、コードからわかるように、これは、この継承の系譜のなかで最も原始的なものというわけではありません。基本的なデータ定義は皆そうですが、そのメソッドは、通常の高水準のMopsワードで定義されてはおらず、効率化のために低水準のワードで設計されています。この理由は、基本的なデータオブジェクトのためのメソッドは、プログラム内で断トツで頻繁に実行されるメソッドだからです。
:class  VAR  super{ longword }
 :m +:  inline{ obj +!} ^base +!  ;m
 :m -:  inline{ obj -!} ^base -!   ;m
;class
このオブジェクトのivarのためのデータオブジェクト宣言が無いことに注意して下さい。これは上位クラスのLongwordが供給してくれるので必要ないのです。メソッド+:と-:はそれぞれ増加と減少に当たりますが、これだけでは変数上のオペレーションとしては不十分なように思われます。ここでもまた、上位クラスであるLongwordがローンレンジャーのごとく救助に現れます。Longwordは、とりわけGet:, Put:,およびClear:のメソッドを既に持っているのです。したがって、VARは暗黙の裡にこれらのメソッドを持っているのです(継承)。Longwordは他にも多くの基本的データクラスにとってジェネリックな上位クラスとなっていて、それらの下位クラスに共通のメソッドの全てを定義しています。VARのクラス定義は、ジェネリックな上位クラスからの継承に依存しているという点で、多くのMopsの基礎的クラスにみられる特徴をみせています。

このほとんど対極にあるのがMenuクラスの定義です。25個くらいあるうちの適当ないくつかを、下に挙げておきましょう。これには普遍的に用いられるワードも含まれています。(この例はPowerMops V.4.xから引かれたものです。もっと後のバージョンのPowerMopsではコードは変わっているかも知れませんが、実例による理解という目的のためには適当な例といえるでしょう。)

メニュークラス定義コード


上位クラスx-Array(execution tokenのアレイ)は、このクラス定義がそのデータ構造に関してx-Arrayを継承していることを示していますが、クラス定義においてはこのアレイのサイズを固定する必要はありません。サイズの特定は、このメニュークラスのオブジェクトのインスタンス化のためにオブジェクト宣言をするまで延期することができますし、普通はそうします。(これは直観的にはアレイ型クラスの特徴ではありません。)サイズの特定によって与えられたメニューオブジェクトのアイテムの数が決まるのですから、プログラムがこの値を自分できめる必要があることは明らかです。

普通は、どんなプログラムもInit:メソッドにあたるメッセージを送るでしょう。これは、メニューオブジェクトの全アイテムに動作を割り当てます。--- 例えば、ファイルメニューの新規アイテムをユーザーが選択した場合のプログラムの反応などです。また、プログラムは大抵、非-リソース-ベースのメニューのためのNew:か、リソース-ベースのメニューのためのGetnew:のどちらかを呼ぶでしょう。どちらのメソッドもメニューレコードをツールボックスに渡します。どちらのメソッドも完全にサポートされています。

リソースベースのメニューについては、プログラマーは、アップルのフリーウェアであるResEditのようなリソースエディタを用いて、‘menu’リソースとして、文字通り各メニューの絵を描きます。これは全く易しく、私には他の方法は考えられません。リソースベースのアプローチは、プログラムがずっと単純になり、全く一般的に用いられています。平均的なMopsプログラムには、ともかく他にもいくつかリソースが必要となるでしょうから、メニューリソースをそれに追加するのもそれほど大変ではありません。

GetItem:とPutItem:メソッドによって、いわば実行時にメニューアイテム名をいじりまわすことができます(これはユーザーを全く混乱させてしまうかも知れませんが)。

プログラムの多くはCheck:およびUncheck:メソッドを用いるでしょう。;前者はクリックされたアイテムの名前の脇にちいさなチェックマークを付け、後者はそれを消します。ワード‘1+’はトップスタック上にある数値に1を加えるための簡便法です。

大文字と小文字で書かれた分かりやすい名前、たとえばGetMenu, InsertMenu, SetMenuItemText, CheckItemなどは全てシステムコール、ないしSyscallです。これらはMacツールボックスサービスを起動します。知っておくべき重要な点は、Mops自体に属する名前と違って、Syscall名は大文字/小文字を識別する(case sensitive)ということです。(徹底してそうです。ツールボックスはMopsでは極めて特異な存在です。)

下位クラスと上位クラス

全てのクラス定義はオブジェクトのひとつのクラスを定義しますが、そのような定義はみな、同時に別のクラス定義の下位クラスでもあります。上位クラスもまたプロト・クラスオブジェクトでない限り、それ自体ひとつの下位クラスです。つまり、全てのユーザー定義クラスと既定クラスは、Objectクラスを除けば、直近の上位クラスを持っているということです。(ひとまずは、多重継承の可能性は措いておきましょう。これをすれば、クラスは複数の直近上位クラスを持つことができます。)

したがって、ユーザー定義クラスは、その上位クラスの連鎖からivarとメソッドをともに継承します(継承とオーバーライド(上書き)については後述)。このことは、(ツールボックス等を通じて)オペレーティングシステムと交渉し、典型的には系譜中にMopsの既定ツールボックスクラスを含んでいるようなユーザー定義クラスには、とくにあてはまります。そのようなクラスは普通は下位クラスと呼ばれたりしますが、これは厳密には観点の問題です。

継承とオーバーライディング

クラス定義はその上位クラス(とその上位クラスとその…上限まで続く)のインスタンス変数とメソッドの両方を継承します。クラス定義は、その上位クラスの名前をオーバーライドしなければなりません。クラス定義はその上位クラスのインスタンス変数をオーバーライドすることはできません。;それは無条件に継承されます。クラス定義は、その上位クラスのメソッドに加えて新しいメソッドを付け足したり、上位クラスのメソッドと同じ名前のメソッドを定義することによって、上位クラスのメソッドのいくつかをオーバーライドすることができます。(Objectクラスだけはひとつもivarを持たないので、そこからは何のivarも継承されないということに注意しましょう)。

実践的留意点としては、下位クラスで定義されることなく継承されるメソッドの数が増えればそれだけ、結果としてできるプログラムは(他が皆同じとすれば)小さくなります。

多重継承

他のオブジェクト指向言語のいくつかとも共通ですが、Mopsは多重継承を提供しています。これによれば、クラスは複数の直近上位クラスを持つことができ、その結果、人が父と母を持ち、その両方から性格を受け継ぐのと同じように、二つあるいはそれ以上の異なる系譜線をもつことができます。多重継承が可能であることによって、二つかそれ以上の既存のクラスに由来する諸機能を新しいクラスにおいて混ぜ合わせることができるようになります。(初めは単一継承クラスで経験を積みましょう。)これは必要な場面では非常に強力な機能で、しばしば、完全なオブジェクト指向実装の試金石とみなされています。Mopsの既定クラスのいくつかは、主としてハイブリッドなデータ構造を得るために、多重継承を採用しています。

メッセージ束縛

Mopsは、早期ないし晩期のメッセージ対オブジェクト結合を達成するための、様々な方法を提供しています。静的(早期)結合(early bind)v.s.動的(晩期)束縛(late bind)は、本質的には実行スピードとプログラミング上の柔軟性とのトレードオフです。晩期結合では、メッセージの受け手は実行時までわかりません(というか、実行時に決まります)。これは、複雑でいくぶんか秘教めいた主題ですから、Mopsは可能な結合モードを広範にサポートしている、といっておけば十分でしょう。有名なSmalltalkシステムは、全ての場合について晩期結合しか提供していません。

結論


一人の人間の努力によるものであるため、Mopsは、最も安定したプログラミングシステム、というわけにはいきません。;他方で、Mopsの"技術サポートスタッフ"は、実際、非常に聡明です。開発者であるMike Horeは、実際の問題のレーポートを快く受け入れることで知られ、通常は、非常に迅速に修正か回避方法を返答してくれます。更に加えて、数多くの経験豊かなMopsユーザーが、システムのあれこれの分かりにくい点について、よろこんで説明ないし助言してくれます。大部のMopsマニュアルは編成替えの余地はあるものの、注意深く読めば十分な情報が含まれており、また大部分が明瞭に記述されています。

私が最初にMopsに出会った頃には、Forthから受け継がれたものである‘逆向き’記法は、控えめにいっても苛つかせるものでしたが、そんなことも全くすぐに克服されてしまいました。スタックプログラミングの側面には、それに十分に習熟してしまった後では、そのあまりの経済性、効率性等々にショックを受けました。しかし、私はなおもオブジェクト指向プログラミングのファンであり、これが、Mopsが私を惹き付ける理由となっています。私は、MopsのクリーンなOOP実装方法が気に入りました。その多くが直観的なのです。

Mopsシステムは四つの部に分かれた370ページを超えるマニュアルを含んでいます。第一部のチュートリアルを除けば、MopsのForth言語的側面を完全にカバーすることは意図されていません。私は、このギャップを埋めるための、よいForthレファレンスが必要であることに気付きました。これについては、FORTH inc.から出ている Forth Programmer's Handbook が、優れた価値のあるものとして推薦できると思います。ともあれ、OOP(あるいはそれを用いるForth)が好きか、あるいは、できれば試してみたいという人には、Mopsは良い‘買い物’であると、私は思います。

著作 © 2002 Edward Williams
邦訳 Nao Sacrada



最終更新:2019年12月20日 22:55