第3章 ブロック
引数をとらないブロック
Smalltalkで最も重要な要素の一つがブロックです。ブロックとは、「一 定の処理をオブジェクト化したもの」です。別の言い方をすると、
- Ruby の do 〜 end ブロック、または { 〜 } ブロック
- Lisp の lambda 関数
- C言語の「関数へのポインタ」
- C++の関数オブジェクト (ファンクタ)
- D言語の delegate
と同じようなものです。作り方は簡単で、処理を [ ] で囲むだけです。 実際に使ってみましょう。
1 |aBlock|
2 aBlock := [Transcript showCR: 'hogehoge'.].
3 aBlock value.
4 !
ここでは、Transcript showCR: 'hogehoge'. という処理を表すブロックを aBlock という変数に束縛しています。ブロックの内容を実行するには、 value メソッドを使用します。
もちろん複数行にわたる処理をブロック内に書くことも出来ます。ブロックを value した結果は、ブロック内の最後の式の返値となります。実際にやってみま しょう。
1 |aBlock|
2 aBlock := [1 + 2. 3 + 4.].
3 Transcript showCR: aBlock value.
4 !
これを実行すると、表示されるのは、3 + 4 の返値である 7 です。(ち なみにブロック内最後の . (ドット) は書かなくても良いのですが、本稿では必 ず書くこととします)
条件分岐
Smalltalkの特徴の一つは、制御文用の構文が存在しないということです。 単純な条件分岐、つまりは if 文に相当するものですら、メソッドとブロックを 組み合わせて実現しています。実例を挙げると、
1 x > 0 ifTrue: [
2 Transcript showCR: 'x is positive'.
3 ].
は、C言語での
if (x > 0) {
printf("x is positive\n");
}
に相当します。x > 0 の返値 (Boolean クラス (の派生クラス) ) には ifTrue: というメソッドがあります。これは、結果が true の場合 にのみ、引数として与えられたブロックを実行するメソッドです。他の言語の if 〜 else に相当するメソッド ifTrue:ifFalse: もあります。
1 x > 0 ifTrue: [
2 Transcript showCR: 'x is positive'.
3 ] ifFalse: [
4 Transcript showCR: 'x is not positive'.
5 ].
しかし、このプログラムはちょっと冗長です。Transcript showCR: の部分が重 複して、邪魔です。ここで、ifTrue:ifFalse: メソッドの返値が、実際に実行さ れたブロックの返値となることを利用して、プログラムを分かりやすくすることが出来ま す。
1 |x|
2 x := 1.0.
3 Transcript showCR: (x > 0 ifTrue: [
4 'x is positive'.
5 ] ifFalse: [
6 'x is not positive'.
7 ]).
8 !
ループ
ループもメソッドとブロックを使って実現します。C言語での
while (x > 0) {
/* 処理 */
}
に相当するコードは、Smalltalkでは
1 [x > 0] whileTrue: [
2 "処理"
3 ].
と書きます。条件式 x > 0 もブロックの中に入っていることに注意してくださ い。これは、ループが一回実行される毎に、条件式も実行される必要があるため です。
もうひとつ、別のループとして、全く同じ処理を何度か繰り返すメソッドを紹介 しましょう。
1 5 timesRepeat: [
2 Transcript showCR: 'hello'.
3 ].
4 !
は、画面に hello と 5 回表示するはずです。timesRepeat: メソッドは 数値を表すクラス (Number) で実装されています。
Smalltalkでは、単純なループや条件分岐すら、標準ライブラリ内のメソッドと して実現されています。ですから、マニュアルを読めば、いろいろな場合に使え るさまざまなループが所狭しと並んでいるのが見えるでしょう。ともかく、単純 なループについてはマニュアルに任せて、本稿ではここまでとします。
論理演算
Smalltalkの論理演算についてまだ紹介していなかったので、それを扱いましょ う。まずは単純な論理 and と or です。
1 |x|
2 x := 1.0.
3 Transcript showCR: 0.0 < x & (x < 2.0);
4 showCR: x < -1.0 | (1.0 <= x).
5 !
真偽値に実装されているメソッド & と | を使用しています。 それぞれ、C言語等の && と || に相当します。やはりこれらも演算子 型のメソッドに過ぎないので、演算順序を明示するために ( ) を使用している ことに注意してください。
しかし、これらは他の多くの言語での and と or とは若干挙動が違います。他 の言語では、
true || 処理
と書くと、「処理」の部分は実行されないことが多いですよね。すでに、or 演 算の左辺が真であることが分かっているので、右辺を実行するまでもなく、|| 演算子は真を返すのです。ですが、Smalltalk の & | メソッドは、繰り返 すようですがただのメソッドに過ぎないので、このような芸当は出来ません。代 わりに使えるのが、and: および or: メソッドです。
1 1.0 > 0.0 or: [Transcript showCR: 'hoge'.].
2 !
これを実行しても、何も表示されないはずです。or: メソッドのレシーバ (1.0 > 0.0) は既に true であるので、[ ] 内を実行せずに true を返すのです。 右辺の処理を実行するかどうかを or: メソッドに委ねるために、右辺はブロッ クにしなければなりません。対応する and: メソッドも同様です。
引数をとるブロック
引数をとるブロックを作ることも出来ます。具体例から挙げましょう。
1 |aBlock|
2 aBlock := [:str | Transcript showCR: str.].
3 aBlock value: 'hoge'.
4 !
画面には hoge と表示されます。まず、引数をとるブロックを作るには、
[:引数名1 :引数名2 … | 処理 ]
と書きます。このようにして作ったブロックを実行するには、value:… メソッドを使用します。例えば、上の例では、aBlock が引数を1つ取るブロック なので、引数を一つ取る value: メソッドを使って、
1 aBlock value: 'hoge'.
としています。さらに多くの引数を取るブロックの場合は、
1 aBlock value: 'hoge' value: 'fuga' value: 'mogera'.
のようにします。
ループ2
これを使って、再びループを取り扱いましょう。まずは簡単な for ループです。 C言語での
int i;
for (i = 1; i <= 5; ++i) {
printf("i = %d\n", i);
}
はSmalltalkでは、
1 1 to: 5 do: [:i |
2 Transcript show: 'i = ';
3 showCR: i.
4 ].
5 !
と書かれます。ここで使用している to:do: メソッドは、1 から 5 まで の数値を順に、ブロックに引数として渡して ( i に束縛して) ブロックを実行していきます。i が例えば 2 ずつ増えるようにするには、
1 1 to: 10 by: 2 do: [:i |
2 Transcript show: 'i = ';
3 showCR: i.
4 ].
5 !
のように、to:by:do: メソッドを使用します。
続いて、いわゆるイテレーションをやってみましょう。Smalltalkの配列 (Array)や連想配列(Dictionary)等 (Collection クラスの派生ク ラス) には、全ての要素に関して同じブロックを次々と実行するメソッ ド do: があります。実際に使ってみましょう。
1 #(100 'hello' 3.14) do: [:element |
2 Transcript showCR: element.
3 ].
4 !
を実行すると、配列 #(100 'hello' 3.14) の要素が次々と表示されたはずです。 do: は、各要素を引数 (ここでは element ) に渡して、 ブロックを実行するメソッドなのです。
他にもより高度なイテレーションをサポートするメソッドがあります。例として は、collect:、select:、reject:、inject:into:、 count:、detect:等です。あまりに多いので、マニュアルを参考 にしてください、ということで逃げておきます。
ところで、C言語などの break 文 (すなわち、ループを途中で強制的に 脱出する方法) に当たるものを紹介していませんでした。 Smalltalk/Xにはこれをサポートする loopWithExit: が用意されているの で、紹介しておきましょう。
1 [:exit |
2 Transcript showCR: 'now looping'.
3 (DialogBox confirm: 'quit this loop?') ifTrue: [
4 exit value: nil.
5 ].
6 ] loopWithExit.
7 Transcript showCR: 'loop ended'.
8 !
ブロック (Block クラス) の loopWithExit: メソッドは、レシーバのブ ロックを実行し続けます (無限ループ)。 ただし、レシーバは 引数を一つ取るブロックでなければなりません。その引数 (ここでは exit ) は、ループから抜け出るための機構を提供します。 (実際はそのためのブロックが渡されます) DialogBox のクラス メソッド confirm: は Yes/No ボタンの用意されたダイアログボックス を表示し、Yes が押されたら true を、No が押されたら false を返します。こ のプログラムでは Yes が押された場合にループから抜けています。ループから 抜けるには、exit の value: メソッドを実行します。引数はループ全体の返 値としたい値です。とりあえず、nil でも渡しておけば良いでしょう。つま り、
1 exit value: nil.
が、C言語等の
break;
に相当しているわけです。
ファイル読み込み
ファイル読み込みを扱ってみましょう。ライブラリが非常に充実しているため、方法も 一通りではありませんが、ここではもっともオブジェクト指向的と思われる方法 を採用しようと思います。
まずは、プレインテキストファイル aFile.txt の各行を > につづけて表示するプログラムです。
1 |stream|
2 stream := 'aFile.txt' asFilename readStream.
3 stream linesDo: [:line |
4 Transcript show: '> ';
5 showCR: line.
6 ].
7 stream close.
8 !
1行目では、文字列を asFilename メソッドで Filename クラス (環境非依存の、ファイル名を扱うクラス)のインスタンスに変 換しています。Filenameクラスの readStream メソッドは、そのファイ ル名に対応する読み込みストリームを返します。2行目の linesDo: はファ イルの各行を line に束縛して、ブロックを実行しています。
このプログラムでは一度ストリームを束縛する変数 stream を用 意しています。これはあまりエレガントな方法ではありません。このプログラム は以下のように短くすることが出来ます。
1 'aFile.txt' asFilename readingLinesDo: [:line |
2 Transcript show: '> ';
3 showCR: line.
4 ].
5 !
Filename クラスの readingLinesDo: は、ストリーム用の変数をあらわに は使わずに、ファイルの各行にたいして同じブロックを実行するメソッドです。
つづいて、> ではなく、各行の頭に行番号を表示するようにプ ログラムを変更しましょう。また、ファイル名もハードコーディングするのでは なく、ファイル選択ダイアログボックスを表示して、ユーザに選んでもらうよう にしましょう。
1 |lineNum|
2 lineNum := 1.
3 DialogBox requestFileName asFilename readingLinesDo: [:line |
4 Transcript show: lineNum;
5 show: ': ';
6 showCR: line.
7 lineNum := lineNum + 1.
8 ].
9 !
DialogBox クラスの requestFileName メソッドはファイル選択ダイアロ グボックスを表示し、選択されたファイル名を文字列で返します。あとは今まで と同様です。
上のプログラムは行番号を保持する変数 lineNum があります。C 言語等では普通かも知れませんが、Smalltalk的にはあまりエレガントではあり ません。一つの方法は以下のようなものです。
1 DialogBox requestFileName asFilename contents keysAndValuesDo: [:lineNum :line |
2 Transcript show: lineNum;
3 show: ': ';
4 showCR: line.
5 ].
6 !
Filename クラスの contents メソッドは、ファイル全体を読み込み、ファ イルの行を並べた配列を返します。keysAndValuesDo: メソッドは配列 (等)のキー(要素のインデックス)と各要素の値 の2つのオブジェクトを引数にして、ブロックを実行してくれます。
