前: Ifelse, 上: Conditionals


5.3 ループと再帰

m4ではループ処理が直接的にはサポートされていませんが、 再帰的なマクロを定義することはできます。 使用しているハードウェアとオペレーティングシステムによるもの以外、 再帰の深さに制限はありません。

ループ処理は再帰とすでに説明した条件構文を使うことで実現できます。

マクロの実引数を反復処理するときには組み込みマクロshiftを 使うことができます。

     shift(...)

このマクロは任意の個数の引数を受け取り、 最初の引数を除く残りの引数をそれぞれクォートしてから、 それらをコンマで区切ったものに展開します。

     shift(bar)
     =>
     shift(foo, bar, baz)
     =>bar,baz

shiftを使った次の例では引数の順番を逆にするマクロを定義しています。

     define(`reverse', `ifelse($#, 0, , $#, 1, ``$1'',
     			  `reverse(shift($@)), `$1'')')
     =>
     reverse
     =>
     reverse(foo)
     =>foo
     reverse(foo, bar, gnats, and gnus)
     =>and gnus, gnats, bar, foo

それほど興味深いマクロではありませんが、 shiftifelseそして再帰を使えばループ処理をどんなに簡単に 実現できるか示しています。

次に挙げるのは、単純なforループを実現するマクロの例です。 単純な数え上げのためなどに使えます。

     forloop(`i', 1, 8, `i ')
     =>1 2 3 4 5 6 7 8

それぞれの引数は順に、反復変数(iteration variable)の名前、 開始値、終了値、そして反復するたびに展開されるテキストです。 このマクロにおいて、マクロiはループ処理の内部でだけ定義されています。 iが以前に値を持っていた場合は、ループが終ればまたその値にもどります。

forloopは次のように入れ子にすることもできます。

     forloop(`i', 1, 4, `forloop(`j', 1, 8, `(i, j) ')
     ')
     =>(1, 1) (1, 2) (1, 3) (1, 4) (1, 5) (1, 6) (1, 7) (1, 8)
     =>(2, 1) (2, 2) (2, 3) (2, 4) (2, 5) (2, 6) (2, 7) (2, 8)
     =>(3, 1) (3, 2) (3, 3) (3, 4) (3, 5) (3, 6) (3, 7) (3, 8)
     =>(4, 1) (4, 2) (4, 3) (4, 4) (4, 5) (4, 6) (4, 7) (4, 8)
     =>

forloopマクロはとても簡潔に実装することができます。 forloopマクロ自体は単なるラッパー(wrapper)で、 第1引数が持つ元の定義を保存してから、内部マクロ_forloopを呼び、 再び保存しておいた第1引数の定義を再確立します。

マクロ_forloopは第4引数を一度展開し、これで反復が終りかどうか調べます。 もし終りでなければ、反復変数を(すでに定義済みのマクロincrを使って) 1増やしてから、自分自身を再帰的に呼び出します。

forloopの実際の実装は次のようになります:

     define(`forloop',
            `pushdef(`$1', `$2')_forloop(`$1', `$2', `$3', `$4')popdef(`$1')')
     define(`_forloop',
            `$4`'ifelse($1, `$3', ,
     		   `define(`$1', incr($1))_forloop(`$1', `$2', `$3', `$4')')')

注意深い引用符の使い方に注目してください。 マクロの引数でクォートされていないのは3つだけで、 それぞれに固有の理由があります。これら3つの引数がクォート されていないのはなぜか、その理由を見つけてください、 これらがクォートされていると、どうなるのかも確かめてみてください。

これら2つのマクロは便利ではありますが、 一般の使用に耐えるほど堅牢ではありません。 開始値が終了値より小さい場合や、第1引数が名前でなかった場合など、 初歩的なエラー対策さえ欠いています。 これら不備の訂正は、読者への課題として残しておきます。