はじめに
この文書ではマルチコア対応 Forth 系言語 Paraphrase にて、どのようにループ処理を書くのか説明します。
注:ver. 0.92.0 にて制御構造を構成するワードの見直しを行いました。ver.0.91.0 以前のコードは修正が必要となりますので、本文書を参考に修正を行って下さい。特に大きな変更は do - loop の無限ループ化です。
for+ 〜 next もしくは for- 〜 next
Forth では do 〜 loop という形でループを実現していましたが、Paraphrase では、カウンタ変数が増えていく(=カウントアップする)場合は for+ 〜 next を、そうではなくカウンタ値が減っていく(=カウントダウンする)場合は for- 〜 next を用います。使用方法は、
開始の整数値 終了の整数値 for+ または for- 繰り返す処理 next
となります。
まずは、1 から 10 までの値を表示するコードを例として示します(一番左側の不等号は Paraphrase インタプリタのプロンプトなので入力する必要はありません(以下同様)):
> 1 10 for+ i . next
1 2 3 4 5 6 7 8 9 10 ok.
>
逆に 10 から 1 までの値を表示するコードは以下のようになります:
> 10 1 for- i . next
10 9 8 7 6 5 4 3 2 1 ok.
>
i はカウンタ値をスタックに積むワードです。変数の様に見えますが変数ではありません。2 重ループにおいても、カウンタ値はワード i です(注 1)。例として、九九を表示するプログラムを以下に示します(99.pp というファイルで保存しておくとします):
1 9 for+
i // 外側のループでも i にてカウンタ値を取得
1 9 for+
dup i * "%2d " putf // 内側のループにおいても i にてカウンタ値を取得
next
drop cr
next
これを実行すると、以下のような出力を得ます:
> "99.pp" load
1 2 3 4 5 6 7 8 9
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81
ok.
>
C や JavaScript 等、一般的なプログラミング言語を使っている方からすると、かなり面食らうかもしれませんが、そもそも Forth では変数を活用するよりもスタックを活用する文化(だと私は感じている)ですので、「こういうものだ」と思って頂ければ助かります。
注 1: Forth では、内側のループから外側のカウンタ値を取得するためのワードとして j というワードが用意されています。現状の Paraphrase (ver.0.92.0)ではこのワードは用意されていません。ニーズがあるようであれば用意したいと思いますので、@paraphrase_lang宛にでも言っていただければ幸いです。
カウンタ値の増加量
for+ もしくは for- においては、通常、カウンタ値はひとつづつ増加または減少していきます。この変化量はワード step にて調整可能です。以下の例では、2 づつ増加させ、1 以上 10 以下の奇数を表示させています:
> 1 10 for+ i . 2 step next
1 3 5 7 9 ok.
>
この例では、9 の次のカウンタ値は 11 となりますので、終了値である 10 よりも大きいと判断されループを抜けます。ループを継続するか否かは for+ または for- で検査されるので、例えば for+ にて開始値が終了値よりも大きな場合は、for+ と next の間の処理は実行されません。
ループからの脱出
ワード leave を用いると、ループ途中で脱出できます。次の例では、for+ ループとしては、1 から 10 までカウントアップするように開始値および終了値は指定されていますが、ループ内の if にてカウンタ値が 5 以上になったら leave にて脱出しています。そのため 1 から 5 までの値しか表示されません:
> 1 10 for+ i . i 5 >= if leave then next
1 2 3 4 5 ok.
>
for{+|-} - next ループの制限
for+ または for- と next は、ワードの定義に使用する場合、同一のワードに所属している必要があります。つまり、有るワードに for+ もしくは for- だけを使用し、他のワードに next のみを書き、2 つのワードを合わせた結果として for{+|-} - next ループを構成することはできません。これはワード step および leave においても同様です。
while{ ... }-> 〜 repeat
カウントアップやカウントダウンに関係なく、ある条件が満たされている場合にのみ処理を繰り返したい場合もあります。そのような場合、Paraphrase には次のような書き方が用意されています:
while{ 条件を確認するコード }-> 条件が満たされている場合に繰り返す処理 repeat
たとえば、空でない文字列が入力されている間、入力された文字列を大文字に変換して出力するような処理はこのように書けます:
> while{ "INPUT: " . get-line not-empty-str? }-> "result=" . >upper . cr repeat
INPUT: hello Paraphrase
result= HELLO PARAPHRASE
INPUT: こんにちは world
result= こんにちは WORLD
INPUT:
ok.
>
get-line は標準出力から取得した文字列をスタックにワードです。not-empty-str? は TOS が空の文字でなければ true をプッシュするワードです。つまり、空文字が入力されるまで "result=" . >upper . cr を繰り返す、という意味になります。
for{+|-} 〜 next ループ同様、leave にてループから脱出できます。次のコードは上のコードと同様、空の文字列が入力されるまで、入力された文字列中の英字を大文字に変換して表示しますが、"end" という文字列が入力された時のみ "BYE" と表示してループを終了するプログラムです:
> while{ get-line not-empty-str? }-> dup "end" == if "BYE" . leave else >upper . cr then repeat
test
TEST
abc
ABC
end
BYE ok.
>
ワード while{ および }-> さらに repeat も for{+|-} - next ループ同様、ワードの定義にて使用する場合、同じワードの定義内で使用する必要があります。ふたつのワードに分けて、それぞれを組み合わせてループを構成することはできません。
備考:このイディオム(成句)は次に示す while - repeat を用いたシンタックスシュガー(構文糖)です。while{ cond }-> 〜 repeat は、{ cond } >r @r>exec while 〜 @r>exec repeat と同等な処理へと展開されます。ワード }-> ではなく、} と書いてしまいがちですので、注意して下さい(何か他に良い方法があれば改良したく思います)。
while - repeat
イディオムは、TOS が true である間だけループしつづける処理を記述できます。実際のコーディングでは、上に述べた while{ ... }-> 〜 loop を使うことが多いと思いますが、このようなワードも用意されています。
これらのワードも、ワードの定義中で使用する場合、同一のワード内で使用しなければなりません。ふたつのワードに分けて、それぞれを組み合わせてループを構成することはできませんので注意して下さい。
while-pipe 〜 repeat
これらのワードで構成されるループは、パイプよりやってくるデータがある限り処理を繰り返すループを構成します。ワード >pipe にて、TOS にある値をパイプに送ることができますので、次のコードでは、1 から 10 までの値をパイプに送り込みます:
> 1 10 for+ i >pipe next
ok.
>
次に、以下のコードを実行すると、パイプにある値が順次取り出され、1 から 10 までの値が表示されます(パイプはスタックと異なり、先入れ先出しのキューなので、1 から順次値が表示されていることに注意して下さい):
> while-pipe . repeat
1 2 3 4 5 6 7 8 9 10 ok.
>
パイプについては並列処理のところで詳しく説明したく思いますが、while-pipe - repeat ループでは、パイプが閉じられるまでループを維持します。パイプが閉じられていない場合、パイプになにも値が存在しない場合は、何か値がパイプに送られるまで待機しつづけます。
do - loop
最後は、無限ループを構成するワード do と loop を紹介します。ワード do と loop を用いて、
do 繰り返す処理 loop
と記述します。do と loop に挟まれた処理は無限に繰り返し実行されます。もちろん、leave にてループを脱出することも可能です。以下のプログラムは、 do - loop を用いて、1 から 5 までを表示するものです:
> 1 do dup . 1+ dup 5 > if drop leave then loop
1 2 3 4 5 ok.
>
他のループ構成用ワード同様、do および loop も、ワード定義で使用する場合は、同一のワード定義で使用する必要があります。
注意:Forth では do - loop はカウンタ値を伴うループを構成しますが、Paraphrase では単なる無限ループを構成します。同じワードでも仕様がことなりますので Forth の知識がある人は注意して下さい。