7
11

More than 1 year has passed since last update.

LaTeXでプログラミングしてみよう(前編)

Last updated at Posted at 2022-03-31

(再投稿です。大幅な加筆・修正してお送りします。)

久しぶりの投稿になります。三条です。

今回の投稿は、約3年前にサークルの部誌に寄稿した記事を改訂したものになります。有料でしたが、まあ3年経ってるので大丈夫だと思います。知らんけど。

少し長いので2部構成にします。前編は準備、後編は実践です。かなり丁寧に書き直したので、TeXマクロ入門みたいな感じになっていると思います。


LaTeXとは

レポートや学術系論文の作成において、LaTeXにお世話になっている方も多いと思います。本稿はそんなLaTeXで普段とは別の使い方で遊んでみようといった内容のお話です。

まず、TeXはフリーの「組版処理システム」であり、文字や図などを紙面に配置する作業をコンピュータ上で行います。しかし、TeX単体では使いにくいので、TeXの機能を拡張して一般的な文書を手軽に作成できるようにしたものがLaTeXです。

一般に文書作成ソフトと言われるとWordを思い浮かべるでしょう。もちろん、Wordは文書を作成することもできますし、図なども挿入することが出来ます。ですが、Wordでは見出しの大きさや書体、適切な空白やインデントなど自分で美しいデザインにするのが面倒なことが多いです。一方、LaTeXでは見出しや文字に統一感があり、フォントや大きさを自動で調節してくれるので、美しい文書に仕上がります。

それでは、この洗練されたLaTeX文書を作成するにはどうすれば良いでしょうか。答えは、「コマンド」です。好きなテキストエディタ上でコマンドを入力し、文書のレイアウトを構成します。そして、LaTeXによってそれらを解釈することによってPDF(文書)を作成することが出来ます。このように、文書の構造をコマンドで指定することから、HTMLと同じように「マークアップ言語」の一種でもあります。

本稿では、「マークアップ言語」としての側面ではなく、「プログラミング言語」としての側面に着目します。つまり、四則演算だけでなくソートすら可能です。そんな意外な一面を持っているLaTeXで少し遊んでみましょう。

環境

TeX Live 2018
macOS Mojave version 10.14.5

準備

LaTeXでプログラミングをするにあたって、いわゆる関数定義やfor文などの構文が書けなければお話になりません。まずは、その基礎知識を紹介します。

世界に挨拶

とりあえず、「Hello, World!」することから始めましょう。

\documentclass[a4paper,uplatex]{jsarticle}
\begin{document}
  Hello, World!
\end{document}

TeXにおけるコマンド(命令)はバックスラッシュから始まり、これは以下の2種類に分けられます。

  • プリミティブ: TeXに元からあるコマンド
  • マクロ: ユーザー定義のコマンド

\begin{document}から\end{document}のブロックは紙面のようなもので、本文は全てここに書きます。documentclass[a4paper,uplatex]{jsarticle}はいわゆるおまじないです。
お好みのテキストエディタに記述したら、ターミナル上で以下のコマンドを実行します。

uplatex hello.tex
dvipdfmx hello.dvi

エラーが出なければ無事にPDFを作成することが出来ます。PDFビューアーで作成したPDFを確認してみましょう。「Hello, World!」と出力されていれば成功です。

マクロ定義

LaTeXにおけるマクロは、\def\<MacroName>#1#2...{<Definition>}または\newcommand{\<MacroName>}[<Argc>][<Arg1>][<Arg2>][...]{<Definition>}1で定義します。

\documentclass[a4paper,uplatex]{jsarticle}
\def\hello#1{%
  #1さん、こんにちは。%
}
\begin{document}
  \hello{太郎}
\end{document}
\documentclass[a4paper,uplatex]{jsarticle}
\newcommand{\hello}[1]{%
  #1さん、こんにちは。%
}
\begin{document}
  \hello{太郎}
\end{document}

出力:
太郎さん、こんにちは。

ポイント

  • \newcommandは同名のマクロが既に定義されている場合はエラーを吐きます。反対に、\defはマクロの定義を上書きするため、既存のマクロの定義を破壊する可能性があります。単純なマクロを定義する際は\newcommandの方が望ましいです。
  • \renewcommand{\<MacroName>}[<Argc>][<Arg1>][<Arg2>][...]{<Definition>}で既にあるコマンドの定義を置き換えます。あくまで「置き換える」だけなので、元からあったマクロの定義を破壊しません。

余談

\defと似たような振る舞いをする\edefというものがあります。こちらは、定義に含まれるマクロを完全展開します。「完全展開」とは、「マクロを定義した時点において、その定義に含まれるマクロを全て展開すること」です。したがって、定義に含まれるマクロを後から再定義しても、\edefで定義した中身は変わりません。2

\def\hoge{HOGE}
\edef\fuga{\hoge}
\fuga % 出力:HOGE

\def\hoge{HOGEHOGE}
\fuga % 出力:HOGE

また、{}によってマクロ定義などを囲むことを「グルーピング」と呼びますが、その中で定義されたマクロや変更されたレジスタの値は「ローカル」なものになります。ただし、マクロ定義の前に\globalを前置すると、その定義は「グローバル」になります。

\def\foo{A}
{
  \def\foo{X}
  \foo % 出力:X
}
\foo % 出力:A

\def\bar{B}
{
  \global\def\bar{Y}
  \bar % 出力:Y
}
\bar % 出力:Y

\gdef\xdefはそれぞれ\global\def\global\edefの短縮系です。

四則演算

次は四則演算です。方法は2通りあります。基本的には好きな方を使ってもらって構いません。

1つ目は従来のTeX言語のようなやり方です。他言語と同様の記法で直感的かつ簡潔に書くことができます。

\documentclass[a4paper,uplatex]{jsarticle}
\newcount\X
\def\calculation#1#2{%
  \X=#1
  \advance\X #2
  \the\X%
}
\begin{document}
  \[
    2 + 3 = \calculation{2}{3}
  \]
\end{document}

出力:

2 + 3 = 5

ポイント:

  • \newcount\<VarName>でカウンタを定義します。同名のカウンタがある場合、後に定義された方が使われます。
  • \advance\<VarName> by <Value>でカウンタに数値を加算します。コンテキストによってはbyは不要です。<Value>に負の値が入ると減算になります。
  • 同様に乗算、除算もサポートされており、それぞれ\multiply\divideが対応します。
  • \theをカウンタの前に前置することで、カウンタの値を出力できます。

2つ目はLaTeX言語のやり方です。定義の前に同名のカウンタがないかチェックが入り、存在すればエラーを吐きます。

\documentclass[a4paper,uplatex]{jsarticle}
\usepackage{calc}
\newcounter{X}
\newcommand{\calculation}[1]{%
  \setcounter{X}{#1}
  \the\value{X}%
}
\begin{document}
  \[
    2 + 3 = \calculation{2+3}
  \]
\end{document}

出力:

2 + 3 = 5

ポイント

  • \newcounter{<VarName>}でカウンタを宣言出来ます。カウンタは変数のようなものです。
  • \setcounter{<VarName>}{<Val>}でカウンタに値を割り当てます。<Val>には整数値が入ります。calcパッケージを使うと、<Val>の部分に式を入れることができるようになります。

条件分岐

条件比較には\ifnum<Condition>または\ifthenelse{<Condition>}{<TrueCase>}{<FalseCase>}3を使います。どちらを使っても良いです。\ifthenelseを使う場合はifthenパッケージが必要です。

\documentclass[a4paper,uplatex]{jsarticle}
\def\adultorchild#1{%
  \ifnum#1<20%
    未成年%
  \else%
    成年%
  \fi%
}
\begin{document}
  18才は\adultorchild{18}です。
\end{document}

出力:
18才は未成年です。

ポイント:

  • 条件式の比較には><=を使うことが出来ます。<=>=は使えません。
  • 数値の比較には\ifnum、文字列の比較には\ifxを使います。その他にも、「奇数かどうか」を判断する場合には\ifoddが用意されています。
  • 条件式が偽となるケースには\elseを使います。必要ない場合は省略可能です。また、構文の終わりには必ず\fiを置きます。うっかり忘れると怒られます。
\documentclass[a4paper,uplatex]{jsarticle}
\usepackage{ifthen}
\newcommand{\adultorchild}[1]{%
  \ifthenelse{#1 < 20}{%
    未成年%
  }{%
    成年%
  }%
}
\begin{document}
  18才は\adultorchild{18}です。
\end{document}

ポイント

  • 条件式の比較には><=の他にも\NOT A < B\NOT A > Bを使うことで、それぞれ>=<=を表せます。
  • ifthenパッケージには論理和、論理積、論理否定が用意されています。それぞれ\OR\AND\NOTを使います。全て大文字です。
  • \ifthenelseの構文は必ず「条件式が真の場合」と「条件式が偽の場合」の二つを記述する必要があります。勝手に片方を省くと怒られます。

繰り返し

繰り返し処理も方法は色々ありますがその中の3通りを紹介します。どの方法も最終的な出力は同じになるようにしています。

1つ目は\@whilenum<Condition>\do{<Stuff>}4です。使う際は\makeatletterが必要です。

\documentclass[a4paper,uplatex]{jsarticle}

\makeatletter

\newcount\Sum
\newcount\K
\def\summation#1#2{%
  \Sum=#1
  \K=#1
  \@whilenum{\K<#2}\do{%
    \advance\K 1
    \advance\Sum \K
  }%
  \the\Sum%
}

\makeatother

\begin{document}
  \[
    \sum_{i=1}^ni=\summation{1}{100}
  \]
\end{document}

出力:

  \sum_{i=1}^{100}i = 5050

ポイント:

  • \@whilenumの条件式には\ifnumと同様に<=や論理否定などを使用することは出来ません。
  • 出力に余計な空白を加えないため、マクロ定義や\doの後ろのカッコにコメント文字(%)を置いています。詳しくは実践編にて。

2つ目はifthenパッケージに同梱されている\whiledo{<Condition>}{<Stuff>}です。

\documentclass[a4paper,uplatex]{jsarticle}
\usepackage{ifthen}

\newcounter{Sum}
\newcounter{K}
\newcommand{\summation}[2]{%
  \setcounter{Sum}{#1}
  \setcounter{K}{#1}
  \whiledo{\value{K} < #2}{%
    \stepcounter{K}
    \addtocounter{Sum}{\value{K}}
  }%
  \the\value{Sum}%
}

\begin{document}
  \[
    \sum_{i=1}^ni=\summation{1}{100}
  \]
\end{document}

ポイント:

  • 使い方は\@whilenumと大して変わりません。ifthenパッケージのマクロなので、条件式の部分に\NOT\ORが使えます。

最後はmultidoパッケージを用いたやり方です。\multido{<Initialization>}{<Repetitions>}{<Stuff>}でC++等の言語でよく見られるforループの要領で記述できます。

\documentclass[a4paper,uplatex]{jsarticle}
\usepackage{multido}

\newcounter{Sum}
\newcommand{\summation}[2]{%
  \setcounter{Sum}{0}
  \multido{\ix=#1+1}{#2}{%
    \addtocounter{Sum}{\ix}
  }%
  \the\value{Sum}
}
\begin{document}
  \[
    \sum_{i=1}^{100}i = \summation{1}{100}
  \]
\end{document}

ポイント:

  • \addtocounter{<ValName>}{<Val>}でカウンタに値を加算します。
  • \stepcounter{<ValName>}でカウンタの値をインクリメントします。
  • \multidoの変数はカンマ区切りで複数の宣言が可能で、\<Var>=<InitialValue>+<Inc>のように初期値を増分を設定します。また、変数の先頭には”型”を表す文字を入れる必要があります。この変数はマクロ扱いなので、参照の際に\valueを使う必要はありません。
  • 型は大きく分けて2つあり、Dimension(\d or \D)とNumber(\n or \N)です。Dimensionは1pt、2cm等の寸法を指定し、Numberは3、7.05等などの数値を指定します。
  • Number変数には、Interger(\i or \I)とReal(\r or \R)という整数と実数に対しての高速版が用意されています。
  • \multidostopを置くと、そのループ処理が終わった後に\multidoを抜けることが出来ます。
  • 現在の繰り返し回数は\multidocountで取得出来ます。カウンタ扱いなので、出力には\theが必要です。
  • <Repetitions>には数値のみが入ります。式を入れることは出来ません。

再帰

もちろん再帰処理も出来ます。しかし、マクロの展開制御が難しいので、慣れないうちはあまりやらない方がいいかもしれません。一般に、再帰処理はwhileループに置き換えることができるので、素直に\@whilenum等を使った方法に直した方が良いでしょう。

次のコードは先ほどの\summationマクロを再帰を用いて記述したものになります。\recsummationが再帰の本体です。

\documentclass[a4paper,uplatex]{jsarticle}

\newcount\Sum
\newcount\K
\newcount\N
\def\summation#1#2{%
  \Sum=0
  \K=#1
  \N=#2
  \recsummation
  \the\Sum%
}
\def\recsummation{%
  \advance\Sum \K
  \ifnum\K<\N%
    \advance\K 1
    \expandafter\recsummation
  \fi
}

\begin{document}
  \[
    \sum_{i=1}^ni=\summation{1}{100}
  \]
\end{document}

ポイント:

  • \expandafter\recsummationの部分は、単に\recsummationとしても動作します。ただし、\summationの第二引数の値を10000程度にすると、後者の場合はスタックオーバーフローします。
  • \expandafter5によって\fiを最初に評価することで、\recsummationが一番最後に呼ばれるようにしています。つまり、末尾再帰になっています。

配列

元々、LaTeXにはデータ構造がありません。そこで、識別子に番号をつけることで擬似的に配列を再現します。これには名前参照という機能を使います。「名前参照」とは、「文字列データを用いて、その文字列と同じ名前をもつ変数を参照すること」です。要は、JavaやRubyで言う「リフレクション」のようなものです。

LaTeXでは\@namedef\@nameuseを使って名前参照を実現できます。次のコードは4つの立法数の和を求めるプログラムです。ここでは擬似配列に格納した値をもとに和を求めます。

\documentclass[a4paper,uplatex]{jsarticle}

\makeatletter

\newcount\Sum
\newcount\K
%% 配列の定義、初期化
\@namedef{arr/0}{1}
\@namedef{arr/1}{8}
\@namedef{arr/2}{27}
\@namedef{arr/3}{64}
%% 4つの立法数の和を求める
\newcommand{\foldarr}{%
  \Sum=0
  \K=0
  \@whilenum{\K<4}\do{%
    \advance\Sum \@nameuse{arr/\the\K}
    \advance\K 1
  }%
  \the\Sum%
}

\makeatother

\begin{document}
  \[
    1^3 + 2^3 + 3^3 + 4^3 = \foldarr
  \]
\end{document}

出力:

1^3 + 2^3 + 3^3 + 4^3 = 100

ポイント:

  • \@namedefは与えられた文字列から成るマクロを定義します。例えば、\@namedef{arr/1}{8}\def\arr/1{8}と展開されます。
  • \@nameuseは与えれた文字列から成るマクロの中身を取り出します。例えば\@nameuse{arr/1}8と展開されます。
  • 数値が来るべき場所に「数字を表す文字列」が来ると、自動的に数値として解釈されます。そのため、\@nameuse{arr/\the\K}は最終的に整数に変換されます。

配列なので値の参照だけでなく値の代入もできます。ですが、代入に関してはちょっとしたテクニックが必要になります。詳しくは後編で紹介しています。

後編に続く

LaTeXでプログラミングをするのに必要な知識を紹介しました。LaTeXでプログラミングしてみよう(後編)では、これらを使って色々と実践します。

参考文献

  1. 省略不可な引数を{}で囲い、省略可能な引数を[]で囲います。

  2. \edefと似たような振る舞いをするものとして\letがありますが、こちらはコマンドを「参照コピー」します。つまり、コピーしたコマンドの中身が変更されると、コピー先も同様に変更されます。

  3. \ifnum\ifxのラッパーです。

  4. LaTeXの内部命令として用意されているマクロです。\loop\repeatを使う従来の方法のラッパーとなっています。内部命令の仲間として、拡張for文のような振る舞いをする\@forもあります。

  5. \expandafter\A\Bにおいて、最初に\Aではなく\Bを展開し、その後\Aを展開するように制御する命令です。

7
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
11