第3章 ブロック

引数をとらないブロック

Smalltalkで最も重要な要素の一つがブロックです。ブロックとは、「一 定の処理をオブジェクト化したもの」です。別の言い方をすると、

と同じようなものです。作り方は簡単で、処理を [ ] で囲むだけです。 実際に使ってみましょう。

   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  &amp; (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つのオブジェクトを引数にして、ブロックを実行してくれます。

wiredBeep/topics/Programming/Smalltalk/ForProgrammers/03-blocks (last edited 2008-01-26 14:30:25 by beeplex)