マクロツイーター

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

expl3な言語で完全展開可能な関数(マクロ)を作る件について (1)

以前に(TeX 言語での)完全展開可能なナベアツ(\NabeAzzX)のところで次のようなことを述べた。マクロを完全展開可能にする場合、代入操作が全く使えなくなる。従って、(純粋な)関数型言語の方法論でプログラミングする必要がある。

  • 完全展開でない \NabeAzz の仕様は: \NabeAzz{40}実行すると、例のナベアツの出力が得られる。つまり、\NabeAzz は手続きである。
  • 完全展開である \NabeAzzX の仕様は: \NabeAzzX{40}展開すると、1 2 {\AhoFont 3} ... {\AhoFont 39} 40 というトークン列になる。つまり、\NabeAzzXトークン列(この場合 40)からトークン列への関数と考えられる。(そして、実際に「関数」として用いようとすると適切な展開制御が必要。)

つまり、「TeX 言語自身は『手続き型』的にも『関数型』的にも記述できるが、完全展開可能なマクロを作ろうとすると、『関数型』的に記述する必要がある」ということである。

そして、この性質は、expl3 の言語に関してもそのまま当てはまる。先日掲載した \NabeAzzX の実装例は、典型的な『関数型』の書き方をしている。以下の Scheme のコードに大体対応している。

;(use srfi-13) (use srfi-42)
(define (xxnz-x-main n)
  (list-ec (: n 1 (+ n 1) 1) (xxnz-process-step n)))
(define (xxnz-process-step n)
  (if (xxnz-int-is-aho? n)
     `(AhoFont ,n)
     n))
(define (xxnz-int-is-aho? n)
  (or (= (modulo n 3) 0)
      (string-index (number->string n) #\3)))
(print (xxnz-x-main 40))
;==> (1 2 (AhoFont 3) 4 ... (AhoFont 39) 40)

expl3 言語で完全展開可能な関数*1を書こうとする場合、必要条件として、「コード中で用いる関数は全て完全展開可能である」ことが必須となる。そして、結局は「適切な展開制御」も必要となってくるのだが、その話はまた後日。

標準モジュールの関数が完全展開可能であるかはリファレンス(「texdoc interface3」で開かれる)を見れば判る。関数名のところに ★ 印があれば「完全展開可能」である。例えば、\bool_if_p:n の項目*2を見ると、\bool_if_p:n の右に ★ がある。すなわち、この関数は(内部で非常に複雑な処理をしているが)完全展開可能である。

これを見て実際に調べた人の中には、\NabeAzzX のコードで最初に使われている \prg_stepwise_function:nnnN には ★ 印でなく中空の ☆ 印が付けられていることに気付いたかも知れない。実は、expl3 言語においては、TeX 言語で「完全展開可能」と呼ばれる概念を、さらに「完全展開可能(fully expandable;★ 印)」と「制限付展開可能(restricted expandable;☆ 印)」に細分している。詳細についてはまた後日。

*1:手続き(変数でなく)として用いられるマクロを expl3 言語では「関数(function)」と呼ぶ。といっても、expl3 言語の枠組で定義される関数が「数学の(/C 言語の/Scheme の)関数」のように「式の一部となって値を表すことが可能」という性質を持っているわけではない。

*2:何故か 2 ヶ所に載っているが、「Boolean expression」の節の方。