Smalltalk
テンプレート:Infobox プログラミング言語 テンプレート:プログラミング言語 Smalltalk(スモールトーク)は、Simula のオブジェクト(およびクラス)、LISPの徹底した動的性、LOGO のタートル操作や描画機能に、アラン・ケイの「メッセージング」というアイデアを組み合わせて作られたクラスベースの純粋オブジェクト指向プログラミング言語、および、それによって記述構築された統合化プログラミング環境の呼称。
Smalltalk で一語であり、「Small Talk」「SmallTalk」などは誤りである。
目次
概要
開発の経緯
ゼロックスのパロアルト研究所(PARC)で1970年代に約10年かけ3世代(Smalltalk-72、76、80)を経て整備された。当初は、ダイナブックである テンプレート:ルビ のオペレーティングシステム的位置付けだったが、Alto のゼロックス社製品としての販売の可能性が同社上層部決定により完全に排除されたこと、発案者であるアラン・ケイの研究開発グループ離脱などを受けてダイナブック色は失せ、Alto のハードウェア技術を基にした商用マシン上で動作するプロの開発者向け統合化プログラミング環境「Smalltalk-80」として1983年に発売されることになる。現在はシンコム[1]より テンプレート:ルビという製品名で主要なオペレーティングシステム向けに販売されている。
Smalltalkの変遷[2][3]
名称 | 備考 |
---|---|
Smalltalk-71 | 最初にSmalltalkの名前を冠したSmalltalk。但しアラン・ケイなど開発者は本当のSmalltalkとしてみなしていない。 |
Smalltalk-72 | アラン・ケイなど開発者からSmalltalkとして認められた最初のSmalltalk。 |
Smalltalk-74 | - |
Smalltalk-76 | - |
Smalltalk-78 | - |
Smalltalk-80 | 現在に知られる仕様となったSmalltalk。 |
ObjectWorks | Smalltalk-80を一般に普及させるために開発・販売されたSmalltalk。 |
VisualWorks | ObjectWorksを引き継ぎ開発されたSmalltalk。Smalltak直系の子孫で現代に至るSmalltalkの中で本家と言える存在である。 |
Apple Smalltalk | Smalltalk-80を元にアップル社が開発したSmalltalk。 |
Squeak | アップル社に移籍したアラン・ケイ、ダン・インガルスらによってApple Smalltalkを元に開発された。Smalltalkの設計者により開発されており、いわば分家と言える存在である。 |
Smalltalk とオブジェクト指向
豊富で整備されたクラスライブラリーは、特にオブジェクト指向プログラミングの手本とされ、デザインパターンの宝庫と称されるまで洗練されたものになっている。また、後世の多くのオブジェクト指向プログラミング言語に直接間接的に多大な影響を与えた。
アラン・ケイが「オブジェクト指向」という言葉を創った当初は、Smalltalk システムが体現した「パーソナルコンピューティングに関わる全てを『オブジェクト』とそれらの間で交わされる『メッセージ送信』によって表現すること」を意味していた。しかしのちに、C++ の設計者として知られるビャーネ・ストロヴストルップが(自身、Smalltalk の影響は受けていないと主張する)C++ の設計を通じて整理し発表した「『継承』機構と『多態性』を付加した『抽象データ型』のスーパーセット」という考え方として広く認知されるようになった(カプセル化、継承、多態性)。現在は、両者の渾然一体化した曖昧な概念として語られることが多い。
Smalltalk の独自性
Smalltalk は、オブジェクトへのメッセージ送信を率直に記述する表記の特殊性や、制御構造をもたずオブジェクトへのメッセージ送信の形で記述する徹底ぶりとも併せて、C言語や C++ などの構造化プログラミングの流れを強く受け継ぐ言語、およびその開発手法に慣れた開発者にとって極めてとっつきにくい言語・環境であるといわれている。このことは、Smalltalk が単なるプログラミング言語ではなく、従来のオペレーティングシステムの概念をも包括する「環境」であることが一つの理由である。Smalltalk を単なる言語としてとらえると、他の言語と比較したとき、使用するオペレーティングシステムのグラフィカルユーザインターフェースに全く従わないなど、その独自性が大きな「欠点」として映る場合もある。
これは VisualWorks や テンプレート:ルビ など、旧来の Smalltalk 環境、つまりダイナブックコンピュータ環境の要素を引き継ぐ統合開発環境を通じて Smalltalk 言語や処理系を学ぶなら、多かれ少なかれ新たなオペレーティングシステムに接するような心構えを持つべきことを意味する。
環境および処理系としてのSmalltalk
Smalltalk 環境から見たSmalltalk 言語
Smalltalk 環境から見た Smalltalk 言語は、いわば bash などのシェルに近い。Smalltalk 環境内であればどこでもマウスで選択した文字列を Smalltalk のソースコードとして実行できるためシェルにコマンドを打ち込んだ時の様に簡単な問い合わせをすぐ実行できるようになっている。
例えばオブジェクト(クラスやメソッドも含む)の構造を調べたければ、そのオブジェクトにテンプレート:ルビセレクターあるいはテンプレート:ルビセレクターを使ったメッセージ式を書き、そのメッセージ式をマウスで選択してdo it(WindowsであればCtrl+Dを押す)すれば良い。
また、設定値の指定に Smalltalk 言語のメッセージ式が使われる。現在設定されている値がメッセージ式として取得でき、値の設定をメッセージ式として指定する。この設定値を指定するメッセージ式は Smalltalk 環境の実行中に設定用のウィンドウから入力される。
仮想機械方式
仮想機械による実行
Smalltalk 環境は、Smalltalk 環境の実行方法として現実のハードウェアに依存している機械語命令を使わず、現実のハードウェアから独立した中間言語命令(仮想機械に対する機械語命令)を仮想機械により実行する仮想機械方式を採用している。 Smalltalk の中間言語命令は、全てイメージファイルというファイルに書き込まれ Smalltalk の仮想機械はそのイメージファイルを読み込んでSmalltalk 環境を実行する。
この仮想機械方式による Smalltalk の実行方法は Smalltalk の言語仕様にも含まれている。[4] なお、Smalltalk が導入したこの仮想機械方式はEULERのpコードマシンからアラン・ケイが着想を得たものである。[5]
イメージファイル
イメージファイルは、Smalltalk 環境の実行状態をそのまま保存したファイルである。イメージファイルは Smalltalk 環境実行中に生成された全てのオブジェクトを直列化して保存することでSmalltalk 環境の実行状態を保存している。この直列化されたオブジェクトには、Smalltalk の中間言語も含まれている。イメージファイルに含まれる中間言語命令は、Smalltalk 自身によって記述されたコンパイラーによってソースコードから翻訳された、バイト列のオブジェクトである。
仮想機械とブートストラップ
中間言語を実行している仮想機械も一般的には Smalltalk で記述される。[6]ただし、仮想機械で動く Smalltalk の実行イメージが実行環境の機械語で直接動作する仮想機械にはなれないため、Smalltalk で書かれたソースコードを一度Cのソースコードに変換し、C言語のソースコードから実行環境で直接動作する仮想機械を生成するという形式をとっている。[7]Smalltalk の実行環境が全く存在しない初期の状況ではコンパイラーも仮想機械も Smalltalk で用意する事はできない。このため Smalltalk の初期段階ではC言語によりコンパイラーや仮想機械が実装されていた。[8]
環境の種類
環境 | 使用している仮想機械 | 備考 |
---|---|---|
ObjectWorks | - | - |
VisualWorks | Object Engine | ゼロックス時代のSmalltalkを引き継ぐSmalltalk環境。名前空間[9]やイメージファイルからモジュールを切り離せるパーセル[10]、OSのデスクトップ上に表示できるウィンドウ等以前のSmalltalkより大きく拡張されている。 |
Squeak | Cog VM | Apple Smalltalkから派生したプラットフォーム非依存やマルチメディア対応を強化したSmalltalk環境。 |
Pharo | Cog VM | Squeakから開発向けに派生したSmalltalk環境。[11] |
Strongtalk | - | 言語仕様に静的型検査を追加したSmalltak環境。[12] |
Little Smalltalk | - | - |
SmallScript(S#) | - | - |
GNU Smalltalk | - | GUI無しでの開発に特化しており他の環境ではGUIを前提としている機能をCUIで実行できる。 |
Concurrent Smalltalk | - | - |
Distributed Smalltalk | - | - |
PIC/Smalltalk | - | - |
Smalltalk Express | - | - |
Ambrai Smalltalk | - | - |
Smalltalk/X | - | Unixで動作するSmalltalk環境。 |
Smalltalk/V | - | pc98で動作するSmalltalk環境。 |
VisualAge Smalltalk | - | Smalltalk/Vから派生しIBMが開発したSmalltalkベースの統合開発環境。Javaで記述されたEclipseの原型でもある。 |
Smalltalk MT | - | Windowsに特化しActive Xを生成できる。[13] |
Dolphin Smalltalk | - | Windowsに特化しておりOS固有の機能やOS固有のGUIを仕様出来る。OSとの親和性が高い。[14][15] |
Smalltalk/JVM | - | Java VMを使用する。[16] |
#Smalltalk | - | .Net Frameworkを使用する。[17] |
言語としてのSmalltalk
言語としての設計思想
言語として Smalltalk が目指したもの。それは計算機を計算機の集合体として構築し、さらに計算機を構成する個々の計算機も計算機の集合体で構築するというように、再帰的な計算機を構築することであった。この再帰的な計算機を構築している無数の計算機は、個々の内部には干渉せずメッセージによる通信のみによって相互作用を発生させ目的の計算を完遂させる。
ここでいう計算機が Smalltalk ではオブジェクトという形で実装された。
この設計思想の誕生は、テンプレート:仮リンクとB5000の設計者らが会談した際の次の発言をアラン・ケイが聞き「計算機の全体を計算機とみなした場合、その計算機の構成要素を計算機に分解するのではなく関数やデータ構造に分解したいと誰が思うのか」と疑問を浮かべた事がきっかけとなっている。 [18] テンプレート:Quotation
ここで言及された再帰という概念は、オブジェクトの成立以外にも Smalltalk の至るところに影響を与えている。
- 反復はメソッドの再帰呼び出し。
- インスタンスオブジェクトを生成しているクラスオブジェクトも、クラスオブジェクトに所属するインスタンスオブジェクトであり再帰関係を持つ。
-
Metaclass
はMetaclass class
のインスタンスオブジェクトでありMetaclass class
は、Metaclass
であり再帰関係を持つ。 - 基本的な派生元となる
Object
やProtoObject
は、それらから派生したUndefinedObject
のインスタンスオブジェクトであるnil
を継承しており再帰関係を持つ。 - Smalltalk 言語は Smalltalk 言語自身によりメッセージ送信等の言語機能が制御される。
- Smalltalk 言語の翻訳や仮想機械は Smalltalk 言語により実装される。
- Smalltalk 言語の大域変数は
Smalltalk
変数に格納されたオブジェクトにより管理されるが、Smalltalk
変数自体も大域変数である。
言語仕様の種類
Smalltalk の言語仕様は原則として非常に単純なため、環境もしくは処理系の相違による互換の有無は、クラスライブラリーの差異程度に由来するもの(ある意味、バージョンの違いもこれも含まれる)から、言語仕様自体の改変に由来のものまで空間的に連続で多岐にわたる。このため、単に Smalltalk として語弊のある場合、一般にその環境および処理系の呼称もしくは商標(必要ならそのバージョン)をして他と区別するために用いる慣習がある。
文法
コメント
「"~"
」のようにダブルクオーテーションでくくった文字列がコメントとして扱われる。
定数表現
主な定数表現には次のようなものがある。
型 | 例 |
---|---|
整数 | 3
|
小数 | 3.4
|
浮動小数点数 | 3.4e5
|
文字 | $a
|
文字列 | 'abc'
|
シンボル | #abc
|
記号を含むシンボル | #'*abc'
|
配列(要素は定数限定) | #( 'This' #is $a 10 )
|
ブロック(引き数なし) | [ 3 + 4 ]
|
ブロック(引き数付き) | [ :x | x + 1 ]
|
定数ではないが、よく用いられるオブジェクトの生成式には次のようなものがある。
型 | 例 |
---|---|
分数 | 3 / 4
|
複素数 | 3 + 4i
|
座標 | 3 @ 4
|
共同体 | 'a' -> 0
|
言語機能の様に見えるが「/
」や「@
」などはただのセレクターであり、Smalltalk の使用者も同様の機能を作ることが出来る。
変数
一時変数は宣言が必要で、「|
」で挿むように記述する。変数への代入は「:=
」。古い処理系では「_
」が使用された(字形は「←」)。
| a b |
a := 3.
b := 4.
擬変数
他の言語で予約語にあたる擬変数は self
、super
、nil
、true
、false
、thisContext
の6つ。self
と super
はそのメソッドを呼び出したメッセージの受け手(レシーバー)を、nil
と true
と false
はそれぞれ UndefinedObject
、True
、False
に属するソルインスタンス(唯一の実体)を、thisContext
は実行中のコンテキスト(スタックフレーム)を参照するのに使える。
self
と super
は同種のオブジェクトだが、メッセージ式でメッセージレシーバーに指定されたときのメソッド検索の起点が異なり、self
ではオブジェクトが属するクラス、super
ではその基底クラスである。
メッセージ式
Smalltalk では「メッセージ式」と呼ばれる書式でコードを記述する。メッセージ式は「レシーバー」に「メッセージ」を送ることを表すためのもので、そのまま
receiver message
と記述する。メッセージはさらに、呼び出されるされるメソッド(処理方法)の名前を表す「メッセージセレクター」[19]と0個以上の引数の組み合わせからなる。ただし Smalltalk の場合必ずしもセレクターとメソッド名は一致しない。セレクターは引数の数だけコロンを自身に含まなければならず、メッセージとして記述する際にはコロンの直後に引数を挿入する。
メッセージ式 | セレクター | 引数 |
---|---|---|
receiver noArg |
noArg |
テンプレート:N/A |
receiver oneArg: arg |
oneArg: |
arg
|
receiver firstArg: arg1 secondArg: arg2 |
firstArg:secondArg: |
arg1 、arg2
|
引数なしのメッセージを単項メッセージ、そのセレクターを単項セレクターと呼び、引数ありのメッセージをキーワードメッセージ、そのセレクターをキーワードセレクターと呼ぶ。メッセージ記述の際に引数の挿入により分断されたキーワードセレクター断片(例えば firstArg:secondArg:
なら firstArg:
と secondArg:
)をキーワードと呼ぶが、あくまで便宜的な呼び名に過ぎず、そうした言語要素は存在しない。他の言語に見られる「キーワード引数」のようなに省略できるものではなく、また引数順を入れ替えられるものでもない。
セレクターは原則としてアルファベットと数字と0個以上(かつ、引数と同数)のコロンから成るが、例外として二項演算を模した記述が可能となるように記号のみから成る引数1つのセレクターを使ってメッセージ式を記述することもできる。これを二項メッセージ、そのセレクターを二項セレクターと呼ぶ。
メッセージ式 | セレクター | 引数 |
---|---|---|
3 + 4 |
+ |
4
|
#( 1 2 3 ), #( 4 5 ) |
, |
#( 4 5 )
|
この場合、上の「3 + 4
」では、「3
」がレシーバーで「+ 4
」がメッセージである。
通常の処理系では、単項メッセージ、二項メッセージ、キーワードメッセージの順で評価される。二項メッセージ間で乗除の優先はない。
メッセージ式 | 結合性を明示した等価の表現 |
---|---|
3 + 4 * 5 min: 6 factorial |
( ( 3 + 4 ) * 5 ) min: ( 6 factorial )
|
セミコロン「;
」でメッセージ式を区切る事により、1個のレシーバーに対して複数のメッセージを送ることが出来る。これをカスケード式という。
| collection |
collection := OrderedCollection
new
add: 0;
add: 1;
add: 2;
add: 3;
add: 4;
yourself.
カスケード式を用いて書いた上記の文は、カスケード式を用いない次の文と等価である。
| collection |
collection := OrderedCollection new.
collection add: 0.
collection add: 1.
collection add: 2.
collection add: 3.
collection add: 4.
制御構文
複数の式を順次実行する場合は、式をピリオドで区切る。メソッドを中断し戻り値を指定するには復帰文「^ 戻り値式
」を使う。言語機能として持つ制御構文は復帰文を除いて存在せず、復帰文以外の制御は制御構文と同等の機能を持ったメッセージ式で代用する。
ブロック
ブロックは、他の言語で言えば無名関数やクロージャーに該当する機能である。ただし Smalltalk のブロックは関数ではなくオブジェクトである事に加え、無名関数というより制御構文としての性格が強くなっている。実行環境によっては、並列実行の基本単位となる。
ブロックは、引き数の数毎に複数定義された「value
」を含むメッセージを送る事で、ブロック内に記述されたメッセージ式を実行し結果を返す。
[ 0 ] value. "-> 0"
[ :value1 | value1 ] value: 1. "-> 1"
[ :value1 :value2 | value1 + value2 ] value: 1 value: 1. "-> 2"
ブロック内の:value1 :value2
は引き数であり、「|
」以降は「value
」を含むメッセージが送られた際実行するメッセージ式である。「value
」を複数ならべたセレクターは4個程度まで( #value:value:value:value:
)しか定義されておらず。5個以上引き数を取る場合は、配列を引き数とする#valueWithArguments:
を使う必要がある。メソッドが値を返す際は、復帰文の記述が必要となるがブロックの場合は値を返すのに復帰文は必要ない。最後に実行されたメッセージ送信の結果あるいは、最後に書かれた値が戻り値となる。ブロックは制御の基本となるオブジェクトであるため、「value
」を含むメソッド以外にも膨大なメソッドを持っている。ただし、後述する他の制御構文はブロックに対し#value
セレクターまたは、#value:
セレクターを使ったメッセージしか送らない。
条件分岐
条件分岐は #ifTrue:ifFalse:
セレクターを用いたメッセージ式として、条件式の結果の真偽値へのメッセージ送信の形で次のように記述する。
3 < 4 ifTrue: [ 5 ] ifFalse: [ 6 ].
ただ、多くの処理系では条件式や反復式はバイトコードレベルでインライン展開される(飛越し命令の表現に置き換えられる)ため、実際に、例えば上のコードを評価した場合に「ifTrue: [5] ifFalse: [6]」というメッセージが送られる訳ではなく、ひいては ifTrue:ifFalse:
という名のメソッドが呼ばれることもないということは、いずれ知っておく必要がある。
ただし、次のようにオブジェクトが変数に束縛されている場合では、オブジェクトが true
あるいは false
であると断定できないためメッセージ送信が実行される。
something: aBoolean
^ aBoolean ifTrue: [ 5 ] ifFalse: [ 6 ].
Smalltalk では nil
がオブジェクトである。これを利用した nil
専用の条件式も存在する。
object := nil.
object ifNil: [ 5 ] ifNotNil: [ 6 ].
object ifNotNilDo: [ :value | value inspect. ].
条件分岐の制御において、他の言語でいうswitch
に直接該当する文は存在しない。多態性を利用して分岐するか、次のように連想配列を利用して分岐するため不要である。
something: aNumber
| switch |
"速度が求められる場合は、初期化済みのDictionaryのオブジェクトをインスタンス変数やクラス変数にキャッシュする。"
switch := Dictionary
new
at: 1 put: [ #a ];
at: 2 put: [ #b ];
at: 3 put: [ #c ];
yourself.
^ ( switch at: aNumber ifAbsent: [ #z ] ) value.
但し一部の処理系では、次のようなswitch
に類似した書き方ができるものも存在する。
something: aNumber
^ aNumber caseOf:
{
[ 1 ]->[ #a ].
[ 2 ]->[ #b ].
[ 3 ]->[ #c ].
}.
反復
反復制御においてfor
に直接該当する文は存在しない。代わりに回数を指定した反復がある。回数を指定した反復は、整数型へのメッセージ送信の形で次の様に記述する。
"100回の反復処理を実行する"
100 timesRepeat:
[
"反復実行する処理"
],
for
に該当する文は存在しないものの、while
に該当する文は存在する。while
に該当する反復は、ブロックに対するメッセージ送信の形で次の様に記述する。
[ true "真偽値を返す式" ] whileTrue:
[
"反復実行する処理"
].
#whileTrue:
セレクターは、条件が真である間反復する事を意味している。逆に条件が偽である間反復する #whileFalse:
というセレクターも存在する。
また do
-while
に該当する文も存在する。do
-while
に該当する反復は、ブロックに対するメッセージ送信の形で次の様に記述する。
[
"反復実行する処理"
"このブロックの実行結果が真である間反復を繰り返す"
] whileTrue.
Smalltalk では条件なしの反復も存在する。無条件反復は、ブロックに対するメッセージ送信の形で次の様に記述する。
[
"反復実行する処理"
] repeat.
Smalltalk では、反復方法が複数存在するが、実は全てメソッドの再帰呼び出しによって実装されているという事になっている。ただし、こちらも #ifTrue:ifFalse
同様実際にメソッドの再帰呼び出しとして実行されていない事が多い。
反復からの脱出
C言語の break
や Perl の last
に相当する反復脱出は thisContext
に対し #return
セレクターを使ったメッセージを送る。
[
thisContext return. "反復を抜ける"
] repeat.
thisContext
には、引き数を取り、引き数を脱出するブロックの戻り値として返す #return:
セレクターや、戻り先のメソッドを指定する #return:to:
セレクターなど多数の return
系セレクターに対応するメソッドが定義されており、他の言語には珍しい多様な反復の脱出方法を備えている。
例外処理機構
Smalltalk にも例外処理機構が存在する。こちらも、その他の構文と同じくメッセージ式とブロックによって実現されている。例外処理は次の様に記述する。
[
Exception signal: '処理失敗'. "例外発生"
]
on: Exception "補足する例外の種類"
do:
[ exception |
"例外を補足した際の処理"
].
なお、例外の制御はメッセージ送信毎に連結リストとして積み上げられたコンテキスト情報の末端のコンテキスト(メソッドスタック)を表す thisContext
オブジェクトを操作し、コンテキストを巻き戻す事で実現されている。
クラスオブジェクトの登録
Smalltalk は、クラスの定義をメッセージ式による実行環境へのクラスオブジェクトの登録として実現する。他の言語と異なりクラスオブジェクトの登録は単なる定義ではなく実行環境に対する操作である。1度クラスオブジェクトを登録してイメージファイルを保存すると、明示的にクラスオブジェクトを削除しないかぎりはクラスオブジェクトがイメージファイルに残り続ける。Smalltalk 環境に対するクラスオブジェクトの登録は次の様に記述する。
"DerivedクラスをSmalltalk環境に登録する例"
Object "基底クラスオブジェクト"
subclass: #Derived "Objectクラスの派生として登録するクラスオブジェクト名の指定"
instanceVariableNames: 'ia ib ic' "インスタンス(実体)オブジェクトに所属する変数名(インスタンス変数)の指定(空白区切り)"
classVariableNames: 'ca cb cc' "クラスオブジェクトと共有する変数名(クラス変数)の指定(空白区切り)"
poolDictionaries: 'pa pb pc' "クラスに所属する変数名(プール変数)の指定(空白区切り)"
category: 'example'. "Smalltalk環境上でクラス名を表示する際にクラスが所属する分類の指定"
poolDictionaries
と category
を除いては、C++ から派生した言語のクラス定義と概ね同じである。特筆すべきはクラス変数とプール変数の扱いである。C++ や Java の利用者が classVariableNames
の名前を初めて見た際、静的変数(クラス変数)を連想しがちである。しかし、C++ や Java においての静的変数(クラス変数)に該当する変数の指定はプール変数でありクラス変数ではない。Smalltalk のクラス変数は、クラス変数の所属するクラスオブジェクトとインスタンスオブジェクトのみで使用される変数であり、クラス変数が所属するクラスオブジェクトから派生したクラスオブジェクトやそのインスタンスオブジェクトでは共有されない。これは、Smalltalk ではクラスがオブジェクトである事と共に、派生クラスのクラスオブジェクトは、基底クラスのクラスオブジェクトから生成された別のオブジェクトである事を意味している。派生したクラスのインスタンスオブジェクトでも変数を共有したい場合はプール変数を使用する。
Smalltalk におけるクラスの作成は、特殊構文ではなく単なるメッセージ送信である。クラスを登録する際のメッセージは、上記の例のように次のセレクターを使用することが多い。
#subclass:instanceVariableNames:classVariableNames:poolDictionaries:category:
しかし、クラスの登録はあくまでメッセージであり自由に作れるため、実行環境には大抵その他のメソッドが用意されている。例えば、近代的な Smalltalk 環境の一つ Pharo では、次のセレクターに対応したメソッドが用意されている。
#subclass:
#subclass:category:
#subclass:instanceVariableNames:
#subclass:instanceVariableNames:classVariableNames:poolDictionaries:category:
#subclass:uses:
#subclass:uses:instanceVariableNames:classVariableNames:poolDictionaries:category:
#variableByteSubclass:instanceVariableNames:classVariableNames:poolDictionaries:category:
#variableByteSubclass:uses:ginstanceVariableNames:classVariableNames:poolDictionaries:category:
#variableSubclass:instanceVariableNames:classVariableNames:poolDictionaries:category:
#variableSubclass:uses:ginstanceVariableNames:classVariableNames:poolDictionaries:category:
#variableWordSubclass:instanceVariableNames:classVariableNames:poolDictionaries:category:
#variableWordSubclass:uses:ginstanceVariableNames:classVariableNames:poolDictionaries:category:
#weakSubclass:instanceVariableNames:classVariableNames:poolDictionaries:category:
#weakSubclass:uses:ginstanceVariableNames:classVariableNames:poolDictionaries:category:
クラスオブジェクトには、クラス変数とは別途、クラスオブジェクトが Class
クラスから派生したインスタンスとして状態を持つためのインスタンス変数がある。このクラスオブジェクトのインスタンス変数はクラスオブジェクト内だけで共有され、インスタンスオブジェクトからは直接使用できない。
Smalltalk 環境に対するクラスオブジェクトのインスタンス変数の登録は次の様に記述する。
Derived class instanceVariableNames: 'ia ib ic'. "クラスオブジェクトのインスタンス変数名(空白区切り)"
メソッドの登録
メソッド(処理方法)の登録は、コード文字列を引数として与えたクラスへのメッセージ送信でも行えるが、通常は環境に組み込まれたクラスブラウザ(システムブラウザ)と呼ばれるGUIツールを用いる。
メソッドは、「メッセージパターン」と呼ばれるメッセージ式のメッセージ部分を模した書式に続けて0個以上のメッセージ式を連ねることで記述する。例えば、前出の、レシーバーか引数を比べてより小さな方を返す「min:
」というメソッドの登録は次のようなものになる。
min: anOtherObject
^ self < anOtherObject ifTrue: [ self ] ifFalse: [ anOtherObject ].
一行目の「min: anOtherObject
」がメッセージパターンで、メソッド名(セレクター)と仮引数となる擬変数の宣言を兼ねる。念のためここでメソッド名は「min:」、仮引数となる擬変数名は「anOtherObject
」である。メッセージパターンのあとに処理を続けて書くこともできるが、通常は行を改めて(さらに、ここでは省いたが慣習としてメソッドの説明をするコメントを書き、それに続けて)処理を記述する。
なお、メッセージパターンのみで具体的な処理を記述せずにメソッドを登録した場合を含め、復帰文による明示的な戻り値の指定が無い場合、メソッドは戻り値として常に self
を返す。したがって Smalltalk では値を返さないメソッドを書くことはできない。
クラスオブジェクト
Smalltalkは、多くの動的型付き言語やDelphiの様にクラスがオブジェクトである。このためSmalltalkではインスタンスオブジェクトと同様にクラスオブジェクトを変数に束縛してメッセージを送ることができる。
| object |
object := Something new.
object value. "インスタンスオブジェクトにvalueメッセージを送信"
object := Something.
object value. "クラスオブジェクトにvalueメッセージを送信"
クラスオブジェクトは、オブジェクトにclass
メッセージを送ることでも取得できる。
' ' class. "-> ByteString"
復帰文とブロック
Smalltalk のブロックは一種の制御構文であるという性質上、復帰文が他の言語と比べ極めて異質な振る舞いをする。
example
| block |
block := [ ^ 1 ].
block value. "ブロックを実行"
^ 0.
上記のメソッドが登録されたオブジェクトに#example
セレクターを使ったメッセージが送ると結果としては何が返ってくるか。Smalltalk 以外の言語では 0
が返ってくるのが一般的であるが Smalltalk では 1
が返ってくる。Smalltalk はメソッドの復帰文ではなくブロック内の復帰文によりメソッド自体を抜けてしまうのである。この例では、「block value.
」を評価し、block
中の「^ 1.
」で制御が戻ると example
も処理を中断し結果を返してしまう。そして example
は戻り値として block
が戻した 1
を戻すのである。
callee: aBlock
aBlock value.
^ 2.
caller
| block |
block := [ ^ 1 ].
self callee: block.
^ 0.
上記の様なメソッドをまたいでブロックを評価する場合はどうなるだろうか、この場合も Smalltalk は example
の戻り値として block
の戻り値である1を返す。Smalltalk はブロック内で復帰文が実行された際、ブロックの生成地点の呼び出し元までコンテキストを巻き戻すのである。この特性により Smalltalk では、#ifTrue:ifFalse:
セレクターを使った分岐でメソッドを中断したり #repeat
セレクター等を使った反復処理を復帰文だけで中断する事ができるのである。
exampleBlock
^ [ ^ 1 ].
callee: aBlock
aBlock value.
^ 2.
caller
| block |
block := self exampleBlock.
self callee: block.
^ 0.
ただし、上記の様にブロックを生成したコンテキストと、ブロックを評価する際のコンテキストが枝分かれする様な場合は復帰文を実行する事はできない。この場合は BlockContext
が例外を出力し処理が停止してしまう。
メソッドに対する注釈
メソッドに対する注釈(Pragma)は、メッセージ式だけではどうしても実現が難しい機械語でしか記述できない演算子の実装や主記憶領域の確保、仮想機械外部との入出力等の実現や、特定の目的のメソッドを自動で列挙するといった目的で使用される特殊構文である。いくつかの注釈はSmalltalk環境に組み込まれているが、利用者やライブラリーの提供者が注釈を定義する事も出来る。
メソッドに対する注釈はメソッドにしか記述できず、Behaviorでの実行などメッセージ式をメソッド外部で評価している場合には、評価対象の式に注釈を含める事はできない。具体的にはSmalltalk環境のWorkspaceと呼ばれる簡易的にメッセージ式を実行するためのテキストエディターに注釈を記述して評価を実行とするとエラーとなる。
メソッドに対する注釈は次の様に記述する。
< keyword1: arg1 ... keywordN: argN >
<
と>
で囲まれた範囲は、メッセージ式のメッセージ部分と同じになる。このため次の様に引き数を取らない注釈の記述も可能である。
< keyword >
次にメソッドに対する注釈の具体例を挙げる。注釈はSmalltalk環境によって異なり、どの環境でも次の注釈が使えるわけでない事に注意すること。
コード | 意味 |
---|---|
<primitive: 1> |
数値演算など原子的機能の呼び出し。引き数の値は呼び出す機能の番号。 |
<apicall: int 'GetLastError' () module: 'kernel32.dll'> |
FFIによる外部関数の呼び出し。先頭のapicallは呼び出し規約であり注釈の名前ではない。cdeclなどもある。 |
大域変数
Smaltallkでは、Smalltalk環境全体で参照できる大域変数を作成する事が出来る。大域変数は同じく大域変数であるSmalltalk変数に格納されたSmalltalkImage
のインスタンスオブジェクトにメッセージを送って作成する。また、大域変数の削除もSmalltalkImage
のインスタンスオブジェクトに対するメッセージ送信となっている。
Smalltalk at: #globalVariable put: 100.
globalVariable. "->100"
globalVariable := 10.
Smalltalk at: #globalVariable. "->10"
Smalltalk removeAt: #globalVariable.
globalVariable. "->nil"
SmalltalkImage
は一種の連想配列であり、Smalltalkの大域変数は、Smalltalk変数を介す事で連想配列として操作する事が出来るようになっている。
なお、Smalltalkのクラス名と大域変数は同じものであり、クラス名にオブジェクトを代入すれば、そのクラスを破壊してしまうことが出来る。また、Smalltalkオブジェクトが格納されたSmalltlak変数もオブジェクトを代入し破壊する事が出来る。このように大域変数を代入により破壊してしまった場合は、最悪Smalltalk環境が起動しなくなる事態に陥り非常に危険である。このためSmalltalkではよほどの理由がなければ大域変数を使うべきではない。
ファイル用構文
Smalltalk のプログラムは基本的に中間言語としてイメージファイルの中に格納され、ソースコードの編集は Smalltalk のGUI環境から行われる。このため基本的にファイルという形で Smalltalk のソースコードやプログラムを目にすることはない。しかし、ソースコードの交換目的などでどうしても Smalltalk 環境外でソースコードを管理する必要がある場合に備えファイル用の構文が存在する。ファイル用の構文は次のようになる。
!
Object
subclass: #Example
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'example'.
!
! Example methodsFor: 'Instance Methods A' !
selectorA1
"処理"
^ 0.
!
selectorA2: anArgument
"処理"
^ 0.
!!
! Example methodsFor: 'Instance Methods B' !
selectorB1
"処理"
^ 0.
!
selectorB2: anArgument
"処理"
^ 0.
!!
! Example class methodsFor: 'Class Methods' !
selector1
"処理"
^ 0.
!
selector2: anArgument
"処理"
^ 0.
!!
本来他の言語の様なブロックが存在しないため、ブロックとして「!
」が使用される。クラスの登録は「!
」の一組で囲まれる。メソッドはプロトコル毎に「! クラス名 methodsFor: 'プロトコル' ! 〜 !!
」というブロックで囲まれる。一つのプロトコルには複数のメソッドを定義でき、メソッド同士は一個の「!
」によって区切られる。
ちなみに、クラス登録は単なるメッセージ送信であり特別な文ではないため、クラス登録に使用しているブロックも次のように単純なメッセージ送信の記述に使用する事が出来る。
!
'hello' displayNl.
'world' displayNl.
!
ファイル用構文で記述されたメソッドの登録は、可読性や記述性の面からメッセージ式からかけ離れた変則的な構文が使用される。しかし、この変則的な構文を用いなければメソッドを登録できないわけではなく、次のように通常のメッセージ式でメソッドを登録する事も出来る。
!
Example
compile:
'
selectorC: anArgument
^ 0.
'
classified: 'Instance Methods C'.
!
上記では、クラスオブジェクトExample
のプロトコルInstance Methods C
に対し、メソッドselectorC
を登録している。
委譲と継承
Smalltalk において、継承とは特殊な委譲に過ぎない。
Object
subclass: #Derived "DerivedはObjectクラスから派生させる"
instanceVariableNames: '' "オブジェクトに所属する変数は定義しない"
classVariableNames: '' "クラスオブジェクトと共有する変数は定義しない"
poolDictionaries: '' "クラスに所属する変数は定義しない"
category: 'example'. "クラスの分類はexampleとする(今回の名前に意味はない)"
このため、例えば上記のクラスオブジェクトの生成では、Derived
クラスオブジェクトの基底クラスオブジェクトとして Object
を指定しているが、処理系によっては下記の用に #superclass:
メッセージを送る事で、基底クラスに別のクラスオブジェクトを指定する事が出来る。
"superclass: NewBase. メッセージを送り基底クラスを NewBase に変更する事が出来る。"
Derived superclass: NewBase.
処理系により不可能な事もあるがクラスオブジェクトだけでなく、インスタンスオブジェクトから派生することも出来る。
"インスタンスオブジェクトの nil から派生した Derived クラスを Smalltalk 環境に登録する"
nil
subclass: #Derived
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'example'.
なお通常、派生元の基本となるProtoObjectやObjectはnilから派生しており継承関係は再帰的に循環している。
メッセージ
Smalltalk において、メッセージ、セレクター、メソッドはそれぞれ別物である。C++ 系統の言語の様にオブジェクトに対しメッセージを送るという事は単なる比喩ではない。
あるオブジェクトに対し #hello
というセレクターを使ったメッセージを送る事を考える。この時、Smalltalk においては hello
というメソッドが必ず呼ばれる保証はない。例えば「hello」メッセージを受け取るオブジェクトが hello
メソッドを実装していなければレシーバーに指定されたオブジェクトの doesNotUnderstand:
メソッドが呼ばれる事になる。
なお、多くの Smalltalk の処理系では doesNotUnderstand:
の引き数で得られたメッセージを返すだけのクラスが用意されている。例えば Smalltalk 環境のひとつである Pharo ではメッセージ作成用クラス MessageCatcher
が用意されており次のようにメッセージを拾い、他のオブジェクトに渡すことが出来る。
message := MessageCatcher new show: 'text'. "「show: 'message'」がmessageに代入される。"
message sendTo: Transcript. "「Transcript show: 'text'」が実行される。"
また、セレクターとメソッドが独立していることを利用して一つのメソッドを複数のセレクターに結びつける事もできる。
"#onClick:を使ったメッセージをopen:メソッドに転送させる。"
FileEventHandler
addSelector: #onClick:
withMethod: FileEventHandler >> open:.
型付け
プログラミング言語一般の概念として型検査をソースコードの翻訳時に実行するか、実行時に実行するかにより静的型付けと動的型付けという区分が存在する。Smalltalk の場合、変数に対する操作は全てメッセージ送信であり、変数の種類(型)毎にできる操作は決まっていない。また、オブジェクトに対しメッセージを送った場合、そのオブジェクトがメッセージに対応するメソッドを持っていなくとも実行環境がエラーを発生させる事はない。メッセージに対応するメソッドが存在しない場合、例外を出すか無視するかは、クラスに実装されたメソッドの内容次第である。したがって Smalltalk には型付けの概念はない。例えば、Pharo の MessageCatcher
は全てのメッセージを拾うため、どんなメッセージを与えられても例外が発生することはない。ちなみに Smalltalk は基本的に中間言語に翻訳され、翻訳時にエラーを発生させるため構文検査は静的である。
メッセージと制御構文
制御構文の節で述べた通り、Smalltalkの殆どの制御構文はメッセージ式である。Smalltalkに明るくないプログラマーからは言い方や見方を変えただけと捉えられがちであるが、Smalltalkの制御構文は実際にメッセージであるがゆえに究極には制御分の構文要素を次のように変数に分解してしまうことが出来る。
変数に分解した分岐制御の例:
| then else message condition |
then := [ 1 ].
else := [ 2 ].
message := MessageCatcher
new
ifTrue: then
ifFalse: else.
condition := 2 = 2.
^ message sendTo: condition.
これらの変数に分解された構文要素は、どのクラスのオブジェクトで無いといけないという制限はない。送られたメッセージを処理することさえ出来ればあらゆるオブジェクトに置き換える事が出来る。
クラスオブジェクトとMetaclass
クラスオブジェクトもオブジェクトであるため、所属するクラスが存在している。クラスオブジェクトが所属するクラスはMetaclass
というクラスのインスタンスオブジェクトである。
ByteString. "-> ByteString"
ByteString class. "-> ByteString class"
ByteString class class. "-> Metaclass"
Metaclass
も当然ながらクラスに所属しており、再帰的にMetaclass
に属するようになっている。
' ' class. "-> ByteString"
' ' class class. "-> ByteString class"
' ' class class class. "-> Metaclass"
' ' class class class class. "-> Metaclass class"
' ' class class class class class. "-> Metaclass"
' ' class class class class class class. "-> Metaclass class"
クラスオブジェクトが所属するMetaclass
のインスタンスオブジェクトは特殊なオブジェクトであり、クラスの継承階層と同様に継承階層を持っている。
Collection class superclass. "-> Object class"
Object class superclass. "-> ProtoObject class"
ProtoObject class superclass. "-> Class"
定数とオブジェクト
文法の節で述べた通りSmallatalkでは定数も全てオブジェクトである。どんな定数であれ#class
や#inspect
といった基本的なセレクターを使ったメッセージを受け取ることが出来るため、基本的な操作であれば定数と他のオブジェクトを区別する必要はない。
| showClass |
showClass :=
[ :object |
Transcript
show: object class name;
cr.
].
showClass
value: Object new; "-> Object"
value: Object; "-> Object class"
value: nil; "-> UndefinedObject"
value: 0; "-> SmallInteger"
value: 0.0; "-> Float"
value: 0.0e1; "-> Float"
value: $0; "-> Character"
value: ''; "-> ByteString"
value: #a; "-> ByteSymbol"
value: #'a'; "-> ByteSymbol"
value: #(); "-> Array"
value: []. "-> BlockClosure"
Smalltalk の慣習
オブジェクトの生成と初期化
オブジェクトの生成には #new
セレクターを使ったメッセージを使う。他の言語と違い、new
は演算子ではない。
|object|
object := Example new. "Exampleクラスオブジェクトに「new」メッセージを送りオブジェクトを生成。"
Smalltalk ではクラスオブジェクトのメソッドもインスタンスオブジェクトのメソッドと同様に派生クラスによる再定義が可能である。このため new
メソッドを再定義し初期化処理を記載する事が出来る。new
メソッドを再定義してしまうとオブジェクトの生成自体が不可能になるように思われるが、本来 new
メソッドは #basicNew
セレクターを使ったメッセージをBehavior
に送ってオブジェクトを生成しているためオブジェクトの生成手段がなくなるわけではない。このため new
メソッドを再定義しても 「basicNew」メッセージをBehavior
に送ることでインスタンスオブジェクト生成することが出来る。
ただし、実際の初期化に new
メソッドが使われる事は多くない。実際には慣習としてクラスの作者が新たに登録したインスタンス・クリエイションと呼ばれる new
とは別の初期化用メソッドが使用される。インスタンス・クリエイションは一般的なクラスオブジェクトのメソッドであり、そのメソッドの内部で 「new
」メッセージやその他のインスタンス・クリエイションを使って初期化済みのオブジェクトを生成する役割を持っているだけで基本的にその他のメソッドと変わらない。一般的にinstance creation
というプロトコルに登録される。
| number |
number := Number readFrom: '10'. "readFrom:がインスタンス・クリエイション".
Smalltalk では1個のセレクターに対し1個のオブジェクトから複数のメソッドを関連付けられない[20]ため 「new
」メッセージをの送信によりできる初期化は1個のオブジェクトにつき一通りの初期化だけである。このため複数のインスタンス・クリエイションを用意することで用途に応じた複数の初期化方法を提供しているのである。インスタンス・クリエイションは一般的なメソッドのひとつでしかない。このためインスタンス・クリエイション持つクラスオブジェクトはアブストラクト・ファクトリーとして機能する。
具体的には次のように使われる。
defaultDatabase
"既定のデータベースのクラスオブジェクトを定義した派生クラスで再定義可能なメソッド。
オーバーライドされた際は、必ずしもクラスオブジェクトが返されるとは限らず、インスタンス
オブジェクトが返される場合もある。"
^ PostgreSQL.
database
"databaseへの接続を返すメソッド。
defaultDatabaseにより返されたPostgreSQLに対し、インスタンス・クリエイションである
#connect:セレクターを使ったメッセージが送られ、PostgreSQLのインスタンスオブジェクトが生成される。
ただし、defaultDatabaseは、派生クラスによって再定義できるため、#connect:セレクターを使ったメッセージが
必ずしもPostgreSQLクラスオブジェクトに送られるとは限らない。"
^ self defaultDatabase connect: self configuration.
また、Smalltalk はクラスメソッドを上書きできるため、インスタンス・クリエイションを次の様に実装する事で基本的な初期化処理を派生元のクラスに任せることができる。
x: aX y: aY
^ self new
x: aX;
y: aY.
上記は、2次元座標用のクラスオブジェクトのインスタンスオブジェクトを初期化する派生元のクラスオブジェクトに実装されたインスタンス・クリエイションである。このクラスオブジェクトを継承した2次元座標用のクラスオブジェクトでは#x:y:
セレクターを使ったメッセージに対応するメソッドを実装する必要はない。インスタンス・クリエイションを利用したパターンは Smalltalk では広く利用され、いたる所で見ることが出来る。
アクセッサー
Smalltalk では、単一の値を出し入れするメッセージの事を特にアクセッサ―[21]と呼ぶ。引き数の有無により値の入出の方向を区別する。
例:
コード | 意味 |
---|---|
object value. |
オブジェクトから値を取得する |
object value: 10. |
オブジェクトに値を渡す |
Smalltalk においてアクセッサーはその他のメソッドと役割に違いはなく特別な意味を持たないが、プロトコルとして明示的に テンプレート:ルビとして登録される点が特徴的である。
Smalltalk においてアクセッサーはインスタンス変数の出し入れや、クラス変数の単純な出し入れに使用される事は多くない。Smalltalk においてアクセッサーは次の用途でよく使われる。
目的 | 詳細 |
---|---|
インスタンス変数やクラス変数の初期化 | インスタンス変数やクラス変数からの値の取得時にインスタンス変数やクラス変数が nil であればそれらの変数を初期化する。
|
使用するクラス・オブジェクトの抽象化 | クラス・オブジェクトあるいはファクトリーオブジェクトを取得できるアクセッサ―を用意し、アクセッサ―を派生クラスでオーバーライドする事で派生クラスからオブジェクトの生成に使うクラス・オブジェクトを自由に切り替えられるようにする。 |
保管場所の抽象化 | オブジェクトが、インスタンス・クラス・プールのどこで管理されているかを抽象化する意味がある。例えばクラス変数やプール辞書の操作では、クラス変数やプール辞書の操作目的であってもインスタンスオブジェクトのメソッドとしてアクセッサーを用意する。 |
インスタンス変数やクラス変数に対する委譲 | インスタンス変数やクラス変数に対しメッセージを送るアクセッサ―を定義し、複数の箇所でインスタンス変数やクラス変数に対する同じメッセージ送信をしないようにする。これによりself object value value: 1. というような複雑なメッセージをself value: 1. というメッセージに単純化する。
|
属性
Smalltalk では、非常に利用頻度の低いインスタンス変数やクラス変数を管理する方法として属性(テンプレート:Lang-en-short)というパターンが使用される。属性はインスタンス変数やクラス変数などの内部変数の代わりに連想配列によりオブジェクトを保持する仕組みである。属性の意味合いとしては、属性と言うより所有物という意味の方が適切である。
属性の使用例:
! Tag methodsFor: 'accessing' !
id
^ self valueOfProperty: #id.
!
id: aString
self setProperty: #id toValue: aString.
!!
属性が有効な身近な例としてはXMLやHTMLのタグ属性が挙げられる。例えばHTMLの id
属性や onClick
といったイベント属性は、必ずしも全てのタグで使用されることはない。特にイベント属性については一つのHTML上に一切記述されない事もよくある。この様な使用頻度の低い属性のためにオブジェクトに一個一個変数を定義するのは記憶領域の無駄である。ましてや onClick
、onMouseDown
、onMouseUp
等大量に属性があればこの無駄は馬鹿にならない。この様な無駄を省くために Smalltalk では属性というパターンがよく使用される。
この属性の仕組は、原理的に全てのインスタンス変数やクラス変数は全て属性によって表現することが出来る。この点に着目しオブジェクトに所属する変数を全て属性に置き換えた言語が後の Self であり、JavaScript である。これらの言語でオブジェクトに所属する変数を属性と表現するのは、この Smalltalk における属性に由来するもので、属性の仕組みの有無に関わらずインスタンス変数やクラス変数を属性と表現するのは間違いである。
属性は Self や JavaScript においては当たり前の様に使用されている。しかし、Smalltalk においては属性を多用する事はデバックを著しく困難にするため不適切な作法とされており、HTMLの属性の様に本当に使用頻度の低い変数だけを属性で扱い、常用する変数に属性を乱用すべきではないと言われている。[22]
単純な例外処理
Smalltalk 以外の言語において、配列の範囲外にある配列要素の操作や、値の代入されていない連想配列の操作は、次の例のように操作の前に一旦判定を行なって例外処理するか、例外機構を利用する方法が一般的である。
| key |
key := #phoneNumber.
( map contain: key ) ifTrue: [ ^ map at: key ] ifFalse: [ ^ nil ].
一方 Smalltalk では、配列の範囲外操作の様に単純で頻発するような処理では、次のように予めメッセージに例外処理をブロックとして渡してしまう方法が一般的である。
"#phoneNumber に対応する値が無ければ常に nil を返す。map 自身に #phoneNumber が追加される事はない。"
^ map at:#phoneNumber ifAbsent: [ ^ nil ].
予め例外処理をメッセージに含めることで、単純な例外処理をより簡潔なものとしている。
この方法は、Smalltalk 独自の復帰文と組み合わせる事でより柔軟な制御をする事ができる。
次の処理は、連想配列に値が見つかればそれを表示し、値が無ければ何もしないという処理であるが、処理の中断の判定と連想配列からの値の取り出しを一度のメッセージ送信だけで実現している。
| value |
value := map at:#phoneNumber ifAbsent: [ ^ self ].
Transcript
show: value;
cr.
要素の列挙
Smalltalk は反復処理のための基本構文を備えているが、ある値の生成器(入出力等)やある集合要素から要素を取得するときに基本的な反復構文を使う事は稀である。Smalltalk では、値を列挙するために値の生成器や集合要素に送るべきメッセージが概ね決まっており、値を取り出す際は極力、列挙メッセージ(テンプレート:Lang-en-short)を使用する事が作法となっている。
次に列挙メッセージの送信例を示す。
配列に対する列挙メッセージの例:
#( 4 3 2 1 0 ) do:
[ :each |
"配列の要素が1個ずつ each に代入され表示領域(Transcript)に表示される"
Transcript
show: each;
cr.
].
実行結果:
4
3
2
1
0
数値に対する列挙メッセージの例:
( 0 to: 4 ) do:
[ :each |
"配列の要素が1個ずつ each に代入され表示領域(Transcript)に表示される"
Transcript
show: each;
cr.
].
実行結果:
0
1
2
3
4
列挙メッセージで使えるセレクターは #do:
の様に全ての要素にブロックを適用するだけの単純なセレクターだけでなく、現在の集合要素の要素を操作した上で新しい集合要素を作成する #collect:
や、集合要素から条件に一致する要素だけを抜き出し、新しい集合要素を作り出す #select:
、集合要素の中から特定の要素を見つけ出す #detect:ifNone:
など様々なセレクターがある。処理系によっては #groupBy:having:
などSQLに類似したセレクターに対応するメソッドを多数用意しているものもある。Smalltalk では集合要素や値の生成器自体にこれらのメッセージを受け取れるよう実装する事で、集合要素のデータ構造や、生成器の入力元などの構造に依存せず最適な反復処理を実現できるようになっている。これらの列挙機能は近年の言語において foreach
やyeild
、LINQ等言語機能として賄われつつあるが Smalltalk においては、単にライブラリーの慣習として実現されている所が特徴的である。
オブジェクトの変換
Smalltalkでは一般的にオブジェクトに#as〜
というセレクターを使ったメッセージが送られた場合、オブジェクトを別のクラスのオブジェクトに変換する。例えば次の様な変換がある。
Stringの変換による具体例(変換結果は処理系依存):
'abcd' asSymbol. "→Symbolクラスのオブジェクト"
'10' asInteger. "→SignedIntegerクラスのオブジェクト"
'10:00' asTime. "→Timeクラスのオブジェクト"
"以下は処理系によっては存在しない"
'/home' asPath. "→AbsolutePathクラスのオブジェクト"
'http://example.com' asUrl. "→Urlクラスのオブジェクト"
オブジェクトを別のオブジェクトに変換するメソッドやメンバー関数が用意されている事は、Smalltalkに限らず他の言語でも一般的であり珍しい事ではない。Smalltalkの慣習として特徴的なところは、既存のクラスにこのオブジェクトの変換をユーザーやライブラリーの作者が自由に組み込んでいる所である。例えばSmalltalkの処理系であるPharoでは、初期状態で基本的なクラスであるStringに54個ものas〜
で始まるメソッドが定義されている。この大量の変換メソッドは、メソッド追加した際すぐに影響を判断できるためメソッド追加に対し寛容的なSmalltalk独特の空気を象徴している。しかし、既存のクラスにメソッドを追加すればライブラリーをマージした際、意図しない衝突を生むため多用は避けるべきであるとの意見も存在する。[23]
セレクターとオブジェクトを指定したイベント処理
イベントハンドラーを定義する方法として、Smalltalkでは次のようにセレクターと、レシーバーとなるオブジェクトを指定する方法が一般的である。
| view controller |
view := Morph new.
controller := FileControlHandler withOwnerView: view.
"#click:イベントが発生すると、controllerに対し#open:を使ったメッセージを送る。"
view
handler
on: #click:
send: #open:
to: controller.
同様にイベントハンドラーを指定する別の方法としては、ブロックを指定する方法が考えられる。しかし、Smalltalkの文化においてイベントハンドラーにブロックを使う方法は、何をしているかわかりづらくデバッグが仕辛くなるため避けるべきとされ、セレクターとオブジェクトを指定したイベント処理が多用される。[24]
このセレクターとオブジェクトを指定したイベント処理の方法は、Objective-Cの文化にも引き継がれておりCocoa等のライブラリーにて頻繁に目にすることができる。
MVCとMVCから派生した設計方式
MVCは Smalltalk から生まれた、制御(コントローラー)と情報(モデル)、そして情報の表現方法(ビュー)の3つを分離しクラスオブジェクトの再利用性を高め、実行時に情報と表現の組み合わせを変更できるようにした設計方針である。Smalltalk の世界でMVCは更に表現を担当するクラスに既定の制御を取り込む仕組みを持たせることで PluggableMVC へと発展した。
モデル支援機構
Smalltalk はクラスライブラリーの基礎部分からMVCやMVCから派生した設計方式で使用されるモデルの構築を支援する仕組みを持っており、Smalltalk 以外の言語と比べモデルの構築が格段に楽になっている。次にモデルの動作を確認する最低限のコードを示す。
モデルの登録:
!
"単純なモデルのクラスオブジェクトを登録"
Object
subclass: #ValueHolderModel
instanceVariableNames: 'value'
classVariableNames: ''
poolDictionaries: ''
category: 'Models'.
!
! ValueHolderModel class methodsFor: 'accessing' !
defaultValue
^ 0.
!!
! ValueHolderModel methodsFor: 'accessing' !
value
value isNull: [ model := self class defaultValue. ].
^ value.
!
value: aValue
value := aValue.
self changed: #value.
!!
モデルの監視側登録
!
"モデルを監視する単純なクラスオブジェクトを登録"
Object
subclass: #ValueHolderObserver
instanceVariableNames: 'model getSelector'
classVariableNames: ''
poolDictionaries: ''
category: 'Models'.
!
! ValueHolderObserver class methodsFor: 'accessing' !
defaultModel
"model が nil の場合に使用する既定のモデルを返す"
^ ValueHolderModel.
!!
! ValueHolderObserver methodsFor: 'accessing' !
value
model ifNil: [ ^ nil ].
"modelから指定のセレクターで値を取り出す"
^ model perform: getSelector.
!
getState: aGetSelector
"モデルから値を取り出す際のセレクターはシンボルにより外部から指定する"
getSelector := aGetSelector.
!
model
"現在監視対象となっているモデルを返す"
model isNull:
[
model := self class defaultModel new.
model addDependent: self.
].
^ model.
!
model: aModel
"現在監視対象となっているモデルを監視対象から除去し、
aModelに指定されたオブジェクトを監視対象として追加する。"
self model removeDependent: self.
model := aModel.
self model addDependent: self.
"また、通常は新しいモデルからValueHolderObserverにとっての初期値の読み取りを行う。
ここでは、初期値の読み取りの代わりにモデルが持つvalueオブジェクトの内容を表示Window(Transcript)に表示する。"
Transcript
show: self value asString;
cr.
!!
! ValueHolderObserver methodsFor: 'updating' !
update: anAspect
"モデルが存在しないときは更新しない"
model ifNil: [ ^ nil ].
"モデルが更新されると呼び出され、モデルが持つvalueオブジェクトの内容を表示Window(Transcript)に表示する。"
getSelector = anAspect ifTrue:
[
Transcript
show: self value asString;
cr.
].
!!
! ValueHolderObserver class methodsFor: 'instance creation' !
on: aModel getState: aGetSelector
^ self
getState: aGetSelector;
model: aModel.
!!
動作の確認:
| model observer |
model := ValueHolderModel new.
"監視対象にmodelを指定してobserverを生成。
on:getState:内にてmodel valueが返す値、0が表示Window(Transcript)に表示される。"
observer := ValueHolderObserver
on: model
getState: #value.
"modelの値を更新。observerの#update:が実行されmodel valueが返す値、100が表示Window(Transcript)に表示される。"
model value: 100.
モデルの支援機構は全て Object
クラスオブジェクトに実装されており、全てのオブジェクトはモデルとして動作する。つまりクラスオブジェクトもモデルとして使用できるようになっている。
Morphic方式
PluggableMVC は Self へと場を移し、表現と制御そして、表現対象となる情報を1個のオブジェクトで兼任する テンプレート:仮リンク として再設計された。Self によって発展した Morphic は Smalltalk に移植されSqueak系統の Smalltalk 環境で基本GUIシステムを構築している。Self の Morphic はウェブブラウザ―のDOMや JavaScript に大きな影響を与えている。