Factorに入門する(10) スタックを操作しまくる
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も今後とりあげていきます。