PS

TeX で関数型プログラミング

こちらは TeX & LaTeX Advent Calendar 2015 - Adventar の 15 日目の記事です。

lambda-lists

TikZ と格闘中に lambda-lists (lambda.sty) という小さなパッケージを見つけまして、そのドキュメントを読むとマクロの動作がよく分かりましたのでお奨めしたいと思います。

Currying (カリー化)

関数型プログラミングの特徴として currying を駆使することが挙げられます。Currying というのは関数を返す関数を作ること*1です。

マクロはちゃんと currying されている・・・かのように振舞います。つまり

\newcommand*{\foo}[2]{#1#2}

と定義したとき、\foo\foo{1}も(もちろん\foo{1}{2}も)有効な式になります:

\newcommand*{\Apply}[2]{#1{#2}}

\Apply{\foo}{1}{2}% --> 12
\Apply{\foo{1}}{2}% --> 12
\foo{1}{2}% --> 12

Evaluation strategy (評価戦略)

マクロの evaluation strategycall-by-name*2 と思われます。使わない引数は評価されずに捨てられます。例えば

\newcommand*{\Unuse}[1]{}
\def\Error{\errmessage{error!}}% lambda.sty より

としたとき

\Unuse{\Error}

はエラーになりません。

この二つのマクロの性質、currying と call-by-name (だけ!) を利用して不思議なことをするのが lambda-lists です。

ひとまずリストを紹介します。

リスト

lambda-lists には Church encoding というのを使ったリスト構造が用意されています:

\Head{\Listize[1,2,3]}% --> 1

これで可変長引数のマクロを簡単に作れます:

\newcommand*{\Inenv}[2]{\Foldr\Inenvimpl{#2}{\Listize#1}}
\newcommand*{\Inenvimpl}[2]{\begin{#1}#2\end{#1}}

\Inenv{[center,math,aligned]}{a+b=c}% --> a + b = c

ただ[..]を使うとネストした時に面倒になるし、一つしか要素がない時[..]を省略できないので

\newcommand*{\List}[1]{\List@#1,\Listend@\@eolst}
\newcommand*{\Listend@}{}
\def\List@#1,#2\@eolst{%
    \TeXif{\ifx\Listend@#2}{\Singleton{#1}}{\Cons{#1}{\List@#2\@eolst}}%
}

とすると

\Head{\List{1,2,3}}% --> 1
\Head{\List1}% --> 1

と出来ていいのではないかと。,自体をリストの要素としたい時は{,}とすればいいです。

無限リストもちゃんと動きます:

\newcommand*{\PrependComma}[2]{,#1#2}
\newcommand*{\Unlist}[1]{#1\Unlist@{}}
\newcommand*{\Unlist@}[1]{#1\Foldr\PrependComma{}}
\newcommand*{\Iterate}[2]{\Cons{#2}{\Iterate{#1}{#1{#2}}}}
\newcommand*{\appendb}[1]{#1b}
\Unlist{\Take{5}{\Iterate\appendb{a}}}% --> a,ab,abb,abbb,abbbb

Haskell の関数をいくつか参考文献から丸写ししてみましたがうまくいくみたいです:

\newcommand*{\fibs}{\Cons{0}{\Cons{1}{\ZipWith\NumPlus\fibs{\Tail\fibs}}}}
\Unlist{\Take{9}{\fibs}}% --> 0,1,1,2,3,5,8,13,21

注意点

マクロを関数として扱うには制限があります。例えば

\newcounter{Cnt}
\newcommand*{\OneLike}{\stepcounter{Cnt}1}

\stepcounter{Cnt}1をくっ付けた何かを返す関数であって1を返す関数ではありません。\stepcounter{Cnt}が空に展開されるわけではないからです。

不明点

最後に未だ(自分には)分かってないことをいくつか

  • ローカル変数を作れるか
    • 上記の注意点により\edefを挿入すると関数が壊れる。
    • Call-by-name なのでぜひ必要だが・・・。
  • 無名関数を作れるか
  • 算術演算を(現実的な方法で)実装出来るか

参考文献

*1:関数を値として扱えることを含みます

*2:Call-by-need (いわゆる lazy) ではないようです