第2章 変数
ローカル変数
ローカル変数を使用するには、チャンクの冒頭で
1 | var1 var2 var3 |
2 "処理"
3 !
のように、使用したい変数名をスペースで区切ったものを | | で囲みます。変 数の有効範囲はチャンクの終わり(つまり、次の ! まで)です。変数名には基本 的に _ (アンダーバー)は使えないので御注意のほどを。(歴史的な理由 からです。Smalltalk/Xなど最近の処理系では使えるのかな? 未確認) では、変数に値を 代入してみましょう。
1 | aVariable |
2 aVariable := 'hello'.
3 Transcript showCR: aVariable.
4 !
Smalltalkの代入は、C言語のように = ではなく、例えばPascalのように := で 行います。つまり、
変数名 := 式.
と書くわけです。
全ての変数には、初期値として nil と呼ばれるオブジェクトが代入され ています。実際、
1 |aVariable|
2 Transcript showCR: aVariable.
3 !
は画面に nil と表示します。(nil は UndefinedObject クラス の唯一のオブジェクトです)
ここで at: メソッドを紹介しましょう。これは文字列 (Stringクラス) や 配列 (Arrayクラスや OrderedCollectionクラスなど) に備わっているメソッドで、
1 arr at: i
とすることで、配列や文字列の i 番目の要素を返してくれます。つまり、上の コードはC言語などの
arr[i]
に対応するわけです。これを使って、
1 |aString|
2 aString := 'hello'.
3 Transcript showCR: (aString at: 2).
4 !
とすると、正しく2文字目の e が表示されるはずです。(Smalltalkの配列や文字 列の添字は1始まりであることを思いだしてください) では、文字列 aString の 2文字目を例えば大文字に書き換えたいとします。
|aString|
aString := 'hello'.
(aString at: 2) := $E.
"略"
残念ながら、このプログラムは動きません。上で書いた := の構文を見て下さい。 あくまでも、:= の左辺は変数名です。(aString at: 2) のような 式を書いてはいけません。何故このような、C等のプログラミングに慣れ た方には奇異にうつる制限があるかは、もうすぐ明らかになります。ともかく、今 はこの方法が使えないことを覚えておいて下さい。代わりにSmalltalkでは、 at:put: メソッドを使います。
1 |aString|
2 aString := 'hello'.
3 aString at: 2 put: $E.
4 Transcript showCR: aString.
5 !
arr at: i put: obj は、C言語などの arr[i] = obj に対応します。
変数は参照である!
Smalltalkの変数を使う上でもっとも重要なのは、 全ての変数は参照であるという事実です。SmalltalkにはC言語などでい う通常の変数は有りません。全ての変数が参照 (または「ポインタ」と言った方が分 かりやすいという方もいらっしゃるかも知れません) なのです。これを 確かめるために、以下のようなプログラムを実行してみましょう。
1 |aString anotherString|
2 aString := 'hello'.
3 anotherString := aString. "aString をコピー??"
4 aString at: 2 put: $E. "aString を変更"
5 Transcript showCR: anotherString.
6 !
画面には「hEllo」と表示されます。つまりは3行目が曲者で、これは aString を anotherString に「コピー」しているのではないのです。Smalltalkの中では 以下のようなことが起こっています。
1 aString := 'hello'. "オブジェクト 'hello' に名前 aString をつける"
2 anotherString := aString. "aString が表すオブジェクトに別の名前 anotherString をつける"
3 aString at: 2 put: $E. "aString が表すオブジェクトを変更する"
つまり、:= は右辺のオブジェクトを「代入」、すなわちコピーしているのでは なく、右辺のオブジェクトに名前をつけているだけなのです。 aString と anotherString とは全く同じオブジェクトですから、aString を変 更すると anotherString も変更される (されたように見える) のです。これを他 の言語の「代入」という言葉と区別して束縛 (binding) と呼びます。つま り、上のプログラムだと、'hello' というオブジェクトは aString と anotherString という変数に束縛されていると言うのです。以下では 「代入」という言葉は使わずに、常に「束縛」と言います。
これを考慮すると、何故 := の左辺が変数名でなければならないかも分かるでしょ う。:= は変数が参照しているオブジェクト、すなわち変数に束縛されているオ ブジェクトを変更する構文です。ですから、左辺は飽くまでも 変数の名前です。また、:= は (C++の = 演算子と違い) メソッドではない事に注意して下さい。:= はオブジェクトを変数に束縛 するための、特殊な構文です。
Array
Smalltalkで最も基本的な配列を表すクラス Array を扱います。Array クラスのオブジェクトの作り方にはいろいろな方法がありますが、まず new: メソッドを紹介しましょう。new: メソッドは Array クラスのクラ スメソッドで、Array new: n と書くことで、n 個の要素を持つ新たな配列を作 り、それを返してくれます。各要素の初期値は nil です。
1 |anArray|
2 anArray := Array new: 5.
3 Transcript showCR: anArray.
4
5 "いくつかの要素にオブジェクトを束縛してみる"
6 anArray at: 1 put: 3.14159265.
7 anArray at: 3 put: 'hello'.
8 Transcript showCR: anArray.
9
10 "配列の要素数を表示"
11 Transcript showCR: anArray size.
12 !
要素にアクセスするには、文字列 (String クラス)と同様に、at: メソッドや at:put: メソッドを使用します。他にも、全ての要素を一度に書き換える atAllPut: メソッド等、様々な用途に使えるメソッドがたくさん有りま す。詳しくはマニュアルを参照して下さい。
Arrayクラスのオブジェクトを作るときに、あらかじめ各要素の初期値を決めて おくには、with:with:... メソッドを使います。例えば、
1 Array with: 'hello' with: 'hoge' with: 'fuga' with: 'foobar'
は、1番目の要素から順に、'hello' 'hoge' 'fuga' 'foobar' が束縛されている、 要素数が4の配列を返します。Smalltalk/Xには、要素数が1つの配列を作る with: メソッドから、要素数が8個の配列を作る with:with:with:with:with:with:with:with:with: メソッドまでが用意されてい ます。
配列の記法にはもう一つ、リテラル配列(literal array;配列定数)があ ります。これは以下のように使います。
1 anArray := #( 3.14159265 'hello' 123456 ).
このように、各要素をスペースで区切り、全体を #( ) で囲ったものが リテラル配列です。様々なスクリプト言語での記法に少し近いですが、これには問題が有って、 各要素はリテラル(つまり、定数)でなければならな いのです。ですから、例えば、
1 |anElement|
2 anElement := 3.14159265.
3 Transcript showCR: #(123456 'hello' anElement).
4 !
は恐らく、期待の結果は表示してくれないでしょう。
OrderedCollection
Array クラスは原則として、要素数が固定された配列を扱うためのクラスです。 Smalltalk/Xでは Array クラスにも、要素数を変えるメソッド grow: が ありますが、マニュアルによれば、非常に処理速度が遅いため使わないように、 とのことです。可変長配列を使うためには、OrderedCollection クラス を使うのが良いようです。
1 |aCollection|
2 aCollection := OrderedCollection new.
3 aCollection add: 3.14;
4 add: 'hello';
5 addAll: #('hoge' 'fuga').
6 Transcript showCR: aCollection;
7 showCR: aCollection size.
8 !
Smalltalkの(多分)全てのクラスには new クラスメソッドがあります。 new クラスメソッドは新たにオブジェクトを作り、そのオブジェクトを返します。 特に OrderedCollection クラスの new メソッドは、空(要素数 0)の OrderedCollection クラスのオブジェクトを返します。そこに、add: メ ソッドや addAll: メソッドを使って要素を追加しています。カスケード を使っていることに注意してください。OrderedCollection を使う上での注意点 は、new: メソッドの挙動が Array とは少し違うことです。Array new: n とす ると、最初から要素数が n 個の Array が作られますが、OrderedCollection new: n としても、返される OrderedCollection の要素数は 0 です。 OrderedCollection の new: は飽くまで、メモリを最初から要素 n 個分予約す るためのメソッドなのです。(C++のSTLの reserve() メンバ関数のよう なものです) ですから、new: した後に、あらためて必要な数だけ要素 を追加します。(例えば grow: メソッドを使えば良いでしょう)
Dictionary
Dictionary はいわゆる連想配列 (「ハッシュ」と言った方が分か りやすいでしょうか?) を表すクラスです。基本的な使いかたは OrderedCollection と似ていますが、要素のキーとして整数だけでなく、 任意のオブジェクトを使用できる点が違います。また、新たな要素を追加するの にも add: などのメソッドを使わず、そのまま at:put: すれば良いというのも 特徴です。(例えば、C++ STLの map クラステンプレートのようなもので す)
1 |aDictionary|
2 aDictionary := Dictionary new.
3 aDictionary at: 3.14 put: 'hello';
4 at: 'hoge' put: 'fuga'.
5 Transcript showCR: aDictionary;
6 showCR: aDictionary size.
7 !
このプログラムの aDictionary では、キー 3.14 の要素に 'hello' を、キー 'hoge' の要素に 'fuga' を束縛しています。Dictionary に要素を追加するとこ ろは、もう少し分かりやすく、
1 |aDictionary|
2 aDictionary := Dictionary new.
3 aDictionary add: 3.14 -> 'hello';
4 add: 'hoge' -> 'fuga'.
5 Transcript showCR: aDictionary;
6 showCR: aDictionary size.
7 !
のように書き換えることが出来ます。-> メソッドは a -> b と使わ れたとき、a をキー、b を要素とするペア (Association クラスのオブジェ クト) を返します。
グローバル変数
Smalltalk ではグローバル変数は Dictionary と同じようにして管理されています。 まず、新たなグローバル変数を作る方法を見てみましょう。
1 Smalltalk at: #aGlobalVariable put: 'hello'.
2 Transcript showCR: aGlobalVariable.
3 !
オブジェクト Smalltalk は、Smalltalkの実行環境自体を表すオブジェ クトだと考えて良いと思います。オブジェクト Smalltalk は (ああ、わ かりにくい!) グローバル変数を、変数名->値 の形で連想配列として 格納した一種の Dictionary として使えます。つまり、ここではグローバル変数 の辞書に aGlobalVariable という名前のグローバル変数を追加し、初期値とし て 'hello' を与えているわけです。以後、グローバル変数にはその名前を aGlobalVariable のような形に書くことでアクセスできます。グローバル変数 を追加するときに使っている # はシンボル (symbol) を表す文字 です。#aGlobalVariable は aGlobalVariable という名前を表すオブ ジェクトを返しているのです。
ファイル
Smalltalkでファイルの入出力を行うには、多くのプログラミング言語と同じく、 ストリームを使用します。ですが、より単純な方法もあるので、ここで はそれを紹介しましょう。まず Filenameクラスの説明です。
1 Transcript showCR: Filename currentDirectory.
2 !
Filename は、プラットフォーム非依存にファイル名を管理するためのクラ スです。Filename の currentDirectory クラスメソッドは、カレントディレク トリを表すファイル名を返します。で、それを表示していますが、(Unix 系のOSであれば) UnixFilename(.) と表示されるだけで、なんの情報も 得られていません。これでは使い物にならないので、絶対パスを表示するように しましょう。
1 Transcript showCR: Filename currentDirectory asAbsoluteFilename.
2 !
これで、カレントディレクトリの絶対パスが表示されたはずです。
ファイルを開くには多くの方法がありますが、最も簡単なのは、Filename の contents メソッドを使用することです。
1 Transcript showCR: 'aFile.txt' asFilename contents.
2 !
このプログラムはカレントディレクトリにある aFile.txt というファイルの内 容を Transcript に表示するものです。まず、文字列 'aFile.txt' の asFilename メソッドを実行しています。asFilename はレシーバの文字 列を Filename に変換したものを返します。Filename の contents メソッドは、 そのファイル名で表されるファイルの内容を文字列 (の配列) で返すものです。
class メソッド
Smalltalkの全てのオブジェクトには class という名前のメソッドが有 ります。class は、レシーバの属するクラスを返します。「クラスを返す」とは 奇怪に聞こえるかも知れませんが、純粋なオブジェクト指向言語である Smalltalkでは、クラスもオブジェクトの一種なのです。(詳しくは後述 するつもりです) とりあえずは簡単に、
1 Transcript showCR: obj class.
と書くことで、オブジェクト obj のクラス名が表示される、と考えれば良いで しょう。class メソッドを使うことで、様々なオブジェクトの属するクラスを知 ることが出来ます。
1 Transcript showCR: 123456 class;
2 showCR: 3.14 class;
3 showCR: 'hello' class;
4 showCR: #(123456 3.14 'hello') class;
5 showCR: OrderedCollection new class.
6 !
クラス階層
今までで使用してきたクラスがどのような階層構造を持っているのか見てみましょ う。次の図は、Smalltalkのクラスの階層のほんの一部です。
UML風に、矢印は 派生クラス→基底クラス という向きに描いています。 Smalltalkでは、全てのクラスが Object という単一のクラスから派生し ています。Object 直下には多くのクラスが有りますが、今まで使ってきたクラ ス達 (のほとんど) は、以下のうちのいずれかの派生クラスです。
- Magnitude
様々な数値(等)を扱うクラスの基底クラスです。 Integer (整数)、Float (小数)、Fraction (分数)等の派生クラスが有り ます。
- Collection
- 複数の要素を格納できるようなオブジェクトを表すクラスの基底クラ スです。例えば、String (文字列)、Array (固定長配列)、 OrderedCollection (可変長配列)、Dictionary (連想配列)等の派生クラ
- スが有ります。
- Stream
- 様々な入出力ストリームを表すクラスの基底クラスです。
- Boolean
真偽値を表すクラスです。Smalltalkの真偽値は true または false で表 されます。-~
- UndefinedObject
- 全ての変数の初期値 nil の属するクラスです。反対に、nil は UndefinedObject クラスの唯一のオブジェクトです。
C言語などでいう「組み込み型」に相当するものがないことに注意してください。 単純な整数(Integer)ですら、ユーザ定義のクラスやライブラリ内のクラスと同 じ扱いです。
Smalltalkの全てのクラスは基底クラスを持っています。上の階層図を見ると、 確かに Dictionary のような複雑なクラスも、Boolean のような単純なクラスも 基底クラスを持っています。でも、Object は? いえいえ、実は Object にも 「基底クラス」があります。nil です。つまり、上と同じ様に描けば、
ということです。不思議ではありませんか? nil は「クラス」ではないのに、 Object の「基底クラス」だなんて。この辺りについてはまた後述したいと考え ています。
