マクロツイーター

はてダから移行した記事の表示が崩れてますが、そのうちに直せればいいのに(えっ)

TeX での「代数的データ型」の扱い (2)

前回の続き)

少し複雑な例:リストのソート

多少複雑なプログラムの例として、リストの挿入ソートを実装したものを挙げておく。(\xx@sl@do@sort がメイン。)注目すべき点として、(TeX としては)割と複雑なデータを扱っているのに、展開制御(\expandafter 等)を全く使わずに実装できているということである。展開は「値」をそのままコードとして実行させて自然と行われている。(あるいは、先述のように「構築子を凍結させて \edef で展開」を行っている。)

(前回の最後に示したプログラムソースへの追加部分である。)
% plain TeX / TeX on LaTeX 共用 (前回のコードの続き)
\catcode`\@=11 %------------------------
%% \xx@sl@do@insert{<値>}{<Consリスト>} : Consリストが昇順に
% 整列されている前提で, 昇順を保ったまま<値>を挿入したConsリスト
% を\xx@sl@res に代入する.
\def\xx@sl@do@insert#1#2{%
  % #2 の値でパターンマッチ:
  % \xx@sl@do@insert{<x>}{\xx@Cons{<y>}{<ys>}}
  %  --> \xx@sl@do@insert@cons{<x>}{<y>}{<ys>}
  \def\xx@Cons{\xx@sl@do@insert@cons{#1}}%
  % \xx@sl@do@insert{<x>}{\xx@Nil}
  %  --> \def\xx@sl@res{\xx@Cons{<x>}{\xx@Nil}}
  \def\xx@Nil{\def\xx@sl@res{\xx@Cons{#1}{\xx@Nil}}}%
  #2}
\def\xx@sl@do@insert@cons#1#2{% {<x>}{<y>}
  \ifnum #1<#2 \let\xx@tempa\xx@sl@do@insert@cons@a
  \else \let\xx@tempa\xx@sl@do@insert@cons@b
  \fi
  \xx@tempa{#1}{#2}}
\def\xx@sl@do@insert@cons@a#1#2#3{% {<x>}{<y>}{<ys>}
  \def\xx@sl@res{\xx@Cons{#1}{\xx@Cons{#2}{#3}}}}
\def\xx@sl@do@insert@cons@b#1#2#3{% {<x>}{<y>}{<ys>}
  \xx@sl@do@insert{#1}{#3}% ここで再帰呼出
  \xx@freeze@Cons
  \edef\xx@sl@res{\xx@Cons{#2}{\xx@sl@res}}}

%% \xx@sl@do@sort{<Consリスト>} : リストを昇順に整列したものを
% \xx@sl@res に代入する.
\def\xx@sl@do@sort#1{%
  % #1 の値でパターンマッチ(先と同様)
  \let\xx@Cons\xx@sl@sort@cons
  \def\xx@Nil{\def\xx@sl@res{\xx@Nil}}%
  #1}
\def\xx@sl@sort@cons#1#2{%
  \xx@sl@do@sort{#2}% ここで再帰呼出
  \xx@sl@do@insert{#1}\xx@sl@res}
\catcode`\@=12 %------------------------

ところで、リストのソートといえば、かなり以前に出題した「TeX の再帰処理の練習問題」の中に「リストのソート」が含まれていた。それは以下のような文字列形式で表されたリストをソートするという課題であった。

\sortList{/3/1/4/1/5/9/}
\show\result %=> macro:->/1/1/3/4/5/9/

この文字列形式とCons形式のリストを相互変換するマクロを実装すれば、この問題を解いたことになる。実際にやってみよう。

% plain TeX / TeX on LaTeX 共用 (前掲のコードの続き)
\catcode`\@=11 %------------------------
% \xx@ifempty{<テキスト>}{<真>}{<偽>} : テキストが空かのテスト。
\def\xx@ifempty#1{%
  \ifx\xx@mt#1\xx@mt \expandafter\@firstoftwo
  \else \expandafter\@secondoftwo
  \fi}
\def\xx@mt{\xx@mt@} % ユニークトークン
\long\def\@firstoftwo#1#2{#1}   %+ LaTeX では定義済
\long\def\@secondoftwo#1#2{#2}  %+

% \xx@sl@do@encode{<リスト>} : 出題形式のリストを構築子形式に変換
%  したものを \xx@sl@res に代入。
\def\xx@sl@do@encode#1{%
  \xx@freeze@Cons
  \edef\xx@sl@res{\xx@sl@encode{#1}}}
\def\xx@sl@empty{/}
\def\xx@sl@encode#1{% 完全展開で処理してしまう
  \xx@sl@encode@a#1\xx@end}
\def\xx@sl@encode@a/#1\xx@end{%
  \xx@ifempty{#1}{\xx@Nil}%
   {\xx@sl@encode@b#1\xx@end}}
\def\xx@sl@encode@b#1/#2\xx@end{%
  \xx@Cons{#1}{\xx@sl@encode@a/#2\xx@end}}
% \xx@sl@do@decode{<リスト>} : 構築子形式のリストを出題形式に変換
%  したものを \xx@sl@res に代入。
\def\xx@sl@do@decode#1{%
  \def\xx@Cons##1##2{/##1##2}\def\xx@Nil{/}%
  \edef\xx@sl@res{#1}}
%<*> \sortList{<リスト>} : メイン。
\def\sortList#1{%
  \xx@sl@do@encode{#1}%
  \xx@sl@do@sort\xx@sl@res
  \xx@sl@do@decode\xx@sl@res
  \let\result\xx@sl@res}
\catcode`\@=12 %------------------------

%% テスト
\sortList{/3/1/4/1/5/9/}
\show\result %=> macro:->/1/1/3/4/5/9/
\bye

もちろん、この問題のように予めリストの表現形式が決められている場合は、別の形式を用いることは得策ではない。さらに、ユーザが直接リストを入力として与える場合は、そこでの「構築子を明示した形式」の使用はあまり適切でないだろう。しかし、プログラムの内部でのみ用いるリストに関しては実装者が自由に表現形式を決められるので、その際には、このような変態的な方法もあったことを覚えておくといいかも知れない。

(続く)