Help us understand the problem. What is going on with this article?

TeXのマクロで無名関数を引数にとる高階関数を擬似的に記述する方法

はじめに

TeXのマクロで無名関数を引数に取るような高階関数を使いたいとき、どのように記述すればいいのかを説明します。

TeXで高階関数を使いたいとき

例えば以下のようなものを記述したいとき、マクロを使わないとTeXコードが冗長になります。


$A$を体$F$上の$n \times m$行列, $B$を$m \times l$行列とする.

A =
\begin{pmatrix}
a_{1,1} & \cdots & a_{1,m}\\
\vdots & & \vdots\\
a_{n,1} & \cdots & a_{n,m}
\end{pmatrix}
B =
\begin{pmatrix}
b_{1,1} & \cdots & b_{1,l}\\
\vdots & & \vdots\\
b_{m,1} & \cdots & b_{m,l}
\end{pmatrix}

このとき, 以下で定める$n \times l$行列を

AB :=
\begin{pmatrix}
\displaystyle\sum_{k=1}^m a_{1,k}b_{k,1} & \cdots & \displaystyle\sum_{k=1}^m a_{1,k}b_{k,l}\\
\vdots & & \vdots\\
\displaystyle\sum_{k=1}^m a_{n,k}b_{k,1} & \cdots & \displaystyle\sum_{k=1}^m a_{n,k}b_{k,l}
\end{pmatrix}

$A$と$B$の積と呼び、$AB$と書く.


上記のような行列A, B, ABを記述する最もオーソドックスなTeXコードはそれぞれ

A =
\begin{pmatrix}
  a_{1,1} & \cdots & a_{1,m}\\
  \vdots  &        & \vdots\\
  a_{n,1} & \cdots & a_{n,m}
\end{pmatrix}
B =
\begin{pmatrix}
  b_{1,1} & \cdots & b_{1,m}\\
  \vdots  &        & \vdots\\
  b_{n,1} & \cdots & b_{n,m}
\end{pmatrix}
AB :=
\begin{pmatrix}
  \displaystyle\sum_{k=1}^m a_{1,k}b_{k,1} & \cdots & \displaystyle\sum_{k=1}^m a_{1,k}b_{k,l}\\
  \vdots                                   &        & \vdots\\
  \displaystyle\sum_{k=1}^m a_{n,k}b_{k,1} & \cdots & \displaystyle\sum_{k=1}^m a_{n,k}b_{k,l}
\end{pmatrix}

となります。このとき、A, B, ABにはそれぞれ\begin{pmatrix} ... \end{pmatrix}などの共通した記述があり、ABにおいては、各i, j成分に対し、\displaystyle\sum_{k=1}^m a_{i,k}b_{k,j}が4回も登場します。

すなわち、これらのコードの違いは各i, j成分と行列の大きさだけです。つまり各i, j成分の記述と行列の大きさを引数に取り、行列を表示するマクロがあれば、もっと楽に記述できるはずです。

高階関数で記述

先ほどの行列を表示するマクロを考えていきます。
マクロ\myMatrixを第一引数#1をi, j成分の値をとってその成分の行列の値を返すマクロ、第二引数#2、第三引数#3をそれぞれ行列の行数、列数とします。
このとき、\myMatrixは以下のように記述できます。

\newcommand\myMatrix[3]{
  \begin{pmatrix}
    #1{1}{1}  & \cdots & #1{1}{#3}\\
    \vdots    &        & \vdots\\
    #1{#2}{1} & \cdots & #1{#2}{#3}
  \end{pmatrix}
}

これを使って行列A, B, ABを記述したいのですが、それぞれ\myMatrixの第一引数にとるマクロを定義する必要があります。

\def\myA#1#2{a_{#1,#2}}
\def\myB#1#2{b_{#1,#2}}
\def\myAB#1#2{\displaystyle\sum_{k=1}^m \myA{#1}{k}\myB{k}{#2}}

もしくはもう少し整理して

\newcommand\elementOf[3]{#1_{#2,#3}}
\newcommand\elementOfProd[6]{\displaystyle\sum_{#3=1}^{#4} #1{#5}{#3}#2{#3}{#6}}

\def\myA{\elementOf{a}}
\def\myB{\elementOf{b}}
\def\myAB{\elementOfProd{\myA}{\myB}{k}{m}}

と記述します。

これを利用し、先ほどの行列A, B, ABを記述すると以下のようになります。

A = \myMatrix{\myA}{n}{m}
B = \myMatrix{\myB}{m}{l}
AB := \myMatrix{\myAB}{n}{l}

元のコードと比べ、だいぶスッキリしました。
しかしながら、毎回マクロを定義するのは大変ですし、1度しか使わないマクロでも名前をつけなくてはいけません。1
そこで、無名関数を使った高階関数で記述できないかを見ていきます。

TeXで無名関数を引数にとるようにみえる関数

実はTeXで無名関数を記述することはできません2。しかし、あたかも無名関数を引数にとっているようなマクロなら記述することができます。

定義の仕方

先ほどの行列の例を見ていきましょう。

まず無名関数を引数に取っているように見えるマクロ\myMatrixAFを以下のように定義します。

\newcommand\myMatrixAF[3]{
  \begingroup
    \def\inputFunctionInMyMatrixAF##1##2{#1}
    \begin{pmatrix}
      \inputFunctionInMyMatrixAF{1}{1}  & \cdots & \inputFunctionInMyMatrixAF{1}{#3}\\
      \vdots                            &        & \vdots\\
      \inputFunctionInMyMatrixAF{#2}{1} & \cdots & \inputFunctionInMyMatrixAF{#2}{#3}
    \end{pmatrix}
  \endgroup
}

もしくは、先ほど定義した元の\myMatrixを使って

\newcommand\myMatrixAF[3]{
  \begingroup
    \def\inputFunctionInMyMatrixAF##1##2{#1}
    \myMatrix{\inputFunctionInMyMatrixAF}{#2}{#3}
  \endgroup
}

と記述します。

具体的には、内部で長ったらしい名前のマクロ\inputFunctionInMyMatrixAFを定義して、これを使って\myMatrixと同じように定義しています。つまり無名関数に無理やり名前をつけているわけです。
このとき\inputFunctionInMyMatrixAFを定義するときの引数は##1, ##2のように#を2つ重ねて記述します。これは元の\myMatrixAFの引数と混同しないようにするためのTeXの仕様です。
また、\inputFunctionInMyMatrixAFの名前が長いのは、外部のマクロ名との衝突を避けるためです。もし同名の外部マクロを\myMatrixAFの引数で使いたい場合、後述する通り、予期せぬ結果になる場合があります。
ちなみに\inputFunctionInMyMatrixAF自体は、\begingroup, \endgroupで囲うことで、このスコープ内のローカル変数として扱うことができます。ただし、その場合、以下のような引数の省略ができないので注意。

\newcommand\myMatrixAF[1]{
  \begingroup
    \def\inputFunctionInMyMatrixAF##1##2{#1}
    \myMatrix{\inputFunctionInMyMatrixAF}
  \endgroup
}
% \begingroupと\endgroupのスコープで区切ってしまっているため、\myMatrixに内部マクロを部分適用したものに引数が渡せず、実行時にエラーになります。

\begingroup, \endgroupを外して内部変数をグローバルにして定義することもできますが、後述するように変数書き換えによる予期せぬ返り値になる場合があるので、特に理由のない限り記述しておくのが無難です。

適用方法

使い方は以下の通りです。

A = \myMatrixAF{a_{#1,#2}}{n}{m}
B = \myMatrixAF{b_{#1,#2}}{m}{l}
AB = \myMatrixAF{\displaystyle\sum_{k=1}^m a_{#1,k}b_{k,#2}}{n}{l}

無名関数部分は#1, #2という引数に取っているように記述できます。

このように記述すると、\myMatrixAF内で、まず全ての#nが、n番目の引数の文字列に、##n#nに置き換わります。そうするとうまく\inputFunctionInMyMatrixAFの定義が通り、想定通りに動作するということです。
この仕様はこのQ&Aを参考にしました。

すでに名前がついているマクロを渡すときは、以下のように引数も記述する必要があります。

A = \myMatrixAF{\myA{#1}{#2}}{n}{m}

応用

例えば、こんな関数も記述できます。

\newcommand\applyfgxyFGxy[4]{
  \begingroup
    \def\inputFunctionFInApplyfgxyFGxy##1##2##3{#1}
    \def\inputFunctionGInApplyfgxyFGxy##1##2{#2}
    \inputFunctionFInApplyfgxyFGxy(\inputFunctionGInApplyfgxyFGxy){#3}{#4}
  \endgroup
}

\renewcommand\myMatrixAF{\applyfgxyFGxy{\myMatrix{##1}{##2}{##3}}}

つまるところの$\lambda f g x y.f g x y$です。

\myMatrixAF内の\myMatrixの引数が##nなのは、\myMatrixAFの定義内なので、##n#nに変換されるためです。#nと書いてしまうと\myMatrixAFの引数としてみなされ、引数の個数が合わずエラーになります。

本来ならば$\lambda f g.f g$で記述したいところなのですが、まず、第3、第4引数を省略するとスコープの仕様で部分適用できません。

また、基本的に$\eta$変換もできず、引数を追加できないため、残念ながらこのように記述するしかないです。

(Qiitaの仕様なのかインライン数式モードにするとたまに勝手に改行されることがあるため、それを回避するために段落を開けています。読みづらくてすみません。)

注意点

この方法で無名関数を引数にとるような高階関数を記述するとき、いくつかの注意点があります。

引数の#の数

上の例で見たように、\def\newcommand\renewcommandなどで括るときは無名関数の引数#の数を増やす必要があります。もっと詳しくいうと、\defなどの内部では#が一つ減った状態で処理されるので、#の数に注意してください。

スコープ化を忘れないこと

\begingroup\endgroupを書かないと内部変数がグローバルになります。
このとき、特に入れ子になるようなものを記述するときに思わぬ事態が起こる場合があります。

\newcommand\myTest[1]{
% \begingroup
  \def\inputFunctionInMyTest##1{#1}
  ( \inputFunctionInMyTest{1} + \inputFunctionInMyTest{2} )
% \endgroup
}

\myTest{\myTest{#1}}
% != ( ( 1 + 1 ) + ( 2 + 2 ) )
% = ( ( 1 + 1 ) + 1 )

\myTestの引数として\myTest{#1}が与えられています。
通常のラムダ計算なら( ( 1 + 1 ) + ( 2 + 2 ) )になるはずですが、実際には( 1 + 1 ) + 1 )になります。
これは、まず外側の\myTestが実行され、\def\inputFunctionInMyTest#1{\myTest{#1}}が実行されます。つまり\inputFunctionInMyTest\myTestになります。そこで1つ目の\inputFunctionInMyTest{1}、つまり\myTest{1}が実行され、( 1 + 1 )が返されます。しかし、この内部で\def\inputFunctionInMyTest#1{1}が実行され、\inputFunctionInMyTestがグローバルに定義されているため上書きされます。2つ目の\inputFunctioninMyTestの実行時にはすでに\myTestでは無く、1を返すマクロに変更されているので、全体として( 1 + 1 ) + 1 )が返ります。

このような想定外の事態を避けるためにも、内部変数はローカルにしておくのがいいでしょう。

内部変数と同じ名前のマクロを引数に取らない

内部変数と同じ名前のマクロを引数に取ると、そのマクロは高階関数内の内部変数として扱われ、想定外の結果をもたらす場合があります。

\def\myTest#1#2{
  \begingroup
    \def\xx##1{#1}
    \xx{#2}
  \endgroup
} % \lambda f x. f x

\def\xx{xx}

\myTest{(1 + #1)}{xx} % = (1 + xx)
\myTest{(1 + #1)}{\xx} % = (1 + (1 + ))

2つ目の\myTest{(1 + #1)}{\xx}の内部では、まず\def\xx#1{(1 + #1)}が実行され、その後\xx{\xx}が実行されます。これを展開すると(1 + \xx)になりますが、このスコープ内で\xxの返り値はxxではなく(1 + #1)を返すマクロとして扱われるため、(1 + )が返されます。

このように、内部変数と同名のマクロを使う場合、特に入れ子を使う場合は同じ変数名が現れる可能性が高いため注意が必要です。

まとめ

TeXのマクロで無名関数を引数に取るような高階関数の記述について解説しました。
本記事で紹介した高階関数は、残念ながら$\eta$変換ができず、式全体の引数の個数を最初に指定しないといけないため、使い勝手は少し悪いです。それでも行列のような、形は決まっていて似たような記述を繰り返す場合に便利なので、ぜひ使ってもらえればと思います。


  1. 今回それぞれ\myA, \myB, \myABを定義しましたが、マクロの部分適用を利用してAB := \myMatrix{\elementOfProd{\elementOf{a}}{\elementOf{b}}{k}{m}}{n}{l}と記述することもできます。しかし、TeXでは無名関数そのものは記述できないため、何かしらの名前付きのマクロが必要です。 

  2. おそらくできないはずです。 

nekonibox
2020年4月より理学博士。 専門は数学で、専攻は数理論理学が最も近いです。 定理証明支援系Coqを用いた形式検証が得意。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away