Factorへの入門第10回。このあたりでわれにかえって、基本的なスタック操作のwordについてしらべます。

FactorはForthの末裔なのでスタックが重要です。Factorにはdata stack, retain stack, call stackの3つのスタックがあるようですが、ふつうに"the stack"というときはdata stackのことです。
Factorのwordはスタックからデータをとり、そしてスタックに結果を残します。これを"stack effect"といいます。
word定義にはstack effectを書くことができます。

: multiple? ( x n -- ? ) mod 0 eq ;

カッコ"()"で囲われた部分がstack effectの記述です。
"--"の左側が実行前、右側が実行後です。
このwordの場合は、スタックの上にある値2つ (xとn)をつかって、結果としてひとつの値をスタックにつみます。
xとnはこの順につまれています。つまり、nがスタックの一番上です。
このwordは、xがnで割り切れるかどうかチェックする関数になります。
modのstack effectは ( x y -- z )なので、特にスタックをいじる必要はありません。

では、"xとyがnを法としたときに合同かどうかチェックする"wordを書こうとおもったらどうでしょうか。rubyみたいな普通の言語で書くとこうですかね。

def modEq?(x, y, n)
x % n == y % n
end

factorで書くとどうでしょう。stack effectは( x y n -- ? )にしましょう。まずは対話環境で考えてみます。

( scratchpad ) 9 8 2
--- Data stack:
9
8
2
( scratchpad )

ここで、9 mod 2 と8 mod 2の結果が欲しいわけです。2は2回使うのでコピーしておきたい。スタックトップの値を単にコピーするwordがあります。

( scratchpad ) dup
--- Data stack:
9
8
2
2

dupはForthから存在する由緒正しいwordです。PostScriptにもあります。stack effectは ( x -- x x )。明解ですね。では次に、沈んでいる8を取り出しましょう。これは、コピーを取り出さなくてもよいです。

( scratchpad ) rot
--- Data stack:
9
2
2
8
( scratchpad )

rotも由緒正しいwordです。stack effectは ( x y z -- z y x )。
スタックトップに8, 2がありますが、順序が逆なので交換しましょう。それからmodをとります。

( scratchpad ) swap
--- Data stack:
9
2
8
2
( scratchpad ) mod
--- Data stack:
9
2
0( scratchpad )

swapのstack effectは ( x y -- y x )。続いて、9 2 0 の順序を0 9 2にしましょう。そこまでくればあとひといき。

( scratchpad ) -rot
--- Data stack:
09
2
( scratchpad ) mod
--- Data stack:
01
( scratchpad ) =
--- Data stack:
f
( scratchpad )

-rotは、スタックトップから3つの値をrotとは逆順にまわします。( x y z -- z x y )ですね。ここまでをwordにまとめてみます。

: modEq? ( x y n -- ? ) dup rot swap mod -rot mod = ;

over ( x y -- x y x )を使うと、こう書くこともできます。1手少ない:-)

: modEq? ( x y n -- ? ) swap over mod -rot mod = ;

いずれにしても、慣れないと判じ物ですね。慣れると、このくらいなら気にならなくなると思います。

Factorには、古典的なshuffle wordsのほかにも(たぶんJoy由来の)combinatorがあります。この例はdip combinatorでこう書くこともできます。

: modEq? ( x y n -- ? ) swap over mod [ mod ] dip = ;

まずswap overでx n y nという使いやすい並びにしておき、modを実行してy mod nをスタックトップにおき、そしてスタックトップをretain stackにいれておいてx mod nを計算する([ mod ] dip)する、という流れです。

keepというcombinatorも使えます。

: modEq? ( x y n -- ? ) [ mod ] keep [ swap ] dip mod = ;

この場合は、まず[ mod ] keepで y mod nを計算し、その上にnをのせます。y mod n の結果をzと書くと、x z n。[ swap ] dip してz x nにして、modする、という流れです。

あ、swapdというshffule wordsがあるのを今発見。stack effectは( x y z -- y x z )で、[ swap ] dipと同じです。

: modEq? ( x y n -- ? ) [ mod ] keep swapd mod = ;

dip使う場合もswapd使うとこんなふうに書けます

: modEq? ( x y n -- ? ) dup [ mod ] dip swapd mod = ;

でもkeep使うほうが短いですね。この例の場合、どうしてもnをコピーしなくてはいけません。そのためにdupやoverを使ってもいいのですが、keepを使えばnを使ったあとに戻してくれるので、dupやoverなどが不要になり、結果として短くかけるのですな。

さて、スタック上のデータを華麗に:-)扱うには、shuffle wordsはもちろんですが、combinatorも適宜駆使するのがきっとFactorらしいにちがいない。dip combinatorsはとりあげましたが、他のcombinatorも今後とりあげていきます。