1. Forthは"データが流れる道具"である
「Forthの構造は制御ではなく、データフローである。」
すなわち、IF や DO LOOP よりも、スタック上のデータがどのように流れ、どのように変形されるか が設計の中心である。
Forthプログラムは、スタックの上でデータが"変換されていく"連続体であり、この流れが滑らかであるほど、プログラムは読みやすく、バグが少なくなる。
2. データフロー思考の基本原則
データフロー中心の思考には、以下の三原則がある。
原則1:データの形を決め、処理はそれに従わせる
制御構造(IFやLOOP)を先に考えるのではなく、
- どのデータが入り
- どのように変形され
- 何が出力されるか
を決めてから処理を組み立てる。
「負数なら0にする」という処理はIFを書く必要がない。
0 MAX \ ( n -- max(n,0) )
これは「データの形に基づく」設計である。
原則2:同じ形のデータには同じ処理を適用する
Thinking Forth は、「データの形が揃っていれば、処理は自然に簡略化できる」と述べる。
例えば、行読み → 数字 → 加算という一連の流れで、各段階の出力を統一すると、高レベルワードは非常に簡単になる。
原則3:制御構造は最小限にし、データ変換で吸収する
IF / ELSE で複雑にするより、データの形を変えることで分岐を減らす のがForth的である。
例えば、計算途中で「ゼロ割を避けたい」とき、IFを書かずにゼロなら1に変換する。
DUP 0= IF DROP 1 THEN
ではなく
DUP 0= ABS + \ ( n -- n|1 ) 0なら1、正の値はそのまま
などがよりForth的である。ただし、処理内容が理解しにくくなる欠点もあるため、名前をつけてカプセル化したり、コメントの活用が重要になる。
3. データフロー設計の手順(実践編)
データフロー設計は次の5ステップで行うとよい。
STEP 1:処理の中心となる"データの形"を決める
例:行を読み、数値にし、合計する処理なら、
行 → 文字列 → 数値 → 加算
というフローを先に決める。
STEP 2:各段階の"変換"を独立ワードにする
: READ-STRING ( -- addr u ) ...
: PARSE-NUMBER ( addr u -- n ) ...
: ADD-SUM ( n -- ) ...
Forthは「変換の連続」であることを意識する。
STEP 3:スタック効果を正確に書き、流れを把握する
各ワードのスタック効果を明記すると、高レベル構造を設計したときに流れが"自然につながる"。
STEP 4:高レベルワードは"変換の連結"として書く
: PROCESS-FILE ( -- total )
0 \ 初期値
BEGIN
READ-STRING WHILE \ 文字列
PARSE-NUMBER \ 数
+ \ 合計
REPEAT ;
制御構造には意味が少なく、本体は"変換の連結"であることが分かる。
STEP 5:制御の複雑さは下位ワードに吸収する
高レベルワードでのIFやDO LOOPは最小限にし、複雑な条件は下位の変換ワードの内部で処理する。
4. データフローの可視化:Thinking Forth流"スタック図"の書き方
スタック効果 ( x y -- z ) の記述はForthにとって必須である。
Thinking Forthでは、スタック図を次のように使うことを推奨する。
可視化1:処理のつながりをマッピングする
例:距離を求める(x y → r)
(x y) → (x^2 y^2) → (+) → (sqrt) → (r)
これをワードに対応させると:
DUP * \ x^2
SWAP DUP * \ y^2
+ FSQRT
スタック図を描くことで、処理の一貫性が確認できる。
可視化2:複雑なスタック操作は"変換"として再定義する
例えば、SWAP ROT OVER が混乱を招く場合はワード化する。
: BAD ( a b c -- d )
ROT + SWAP * ;
: SUM-AND-MUL ( a b c -- d )
ROT + \ ( a b c ) → ( b c a ) → ( b c+a )
* ; \ ( b c+a ) → ( b*(c+a) )
意味ベースの変換に置き換えることで読みやすくなる。
5. 条件分岐を減らす:データ変換で吸収せよ
Thinking Forth は、分岐の乱立を"悪い設計の兆候"とみなす。
例1:「境界値処理」をデータ変換で吸収
数値を0以上の値に変換するには、DUP 0< IF DROP 0 THENよりも
0 MAX
数値を100以下に変換するには、DUP 100 > IF DROP 100 THENよりも
100 MIN
例2:真偽値の統一
Forthの真偽値は 0(偽) と -1(真、全ビット1)である。これもデータの形を統一する発想に基づく。
例3:複雑な条件は小さな判定ワードに分ける
IF x y z 条件ごちゃごちゃ THEN
: VALID? ( x y z -- flag ) ...
: PROCESS-IF-VALID ( x y z -- )
VALID? IF ... THEN ;
条件は"データを変換してflagを返す"ワードにする。
6. 実例:データフローでプログラムを組む
以下に、Thinking Forth 的な書き方を示す。
課題:正の値だけを加算し、負数は無視した合計を求める
\ 悪い例(IFに依存)
: BAD-SUM ( addr n -- total )
0 SWAP \ ( addr 0 n ) 累積値を準備
0 ?DO
OVER I CELLS + @
DUP 0< IF DROP ELSE + THEN
LOOP
SWAP DROP ; \ addrを捨てる
\ 良い例(データ変換)
: >0? ( n -- n|0 ) 0 MAX ; \ 負数を0にする変換
: SUM-ARRAY ( addr n -- total )
0 SWAP \ ( addr 0 n ) 累積値を準備
0 ?DO
OVER I CELLS + @ >0? +
LOOP
SWAP DROP ; \ addrを捨てる
制御構造(IF文)が消え、フローが滑らかになっている。