読者です 読者をやめる 読者になる 読者になる

LaTeXでオブジェクト指向プログラミング

LaTeXのコマンドは複数の単語のものも一続きにしないと一つのシンボルと見なしてもらえないのが不便だったので, 名前空間が使えるというか, 記号で区切った後にサブコマンドを続ける書き方をサポートするものを作っていた. そうしたらいつの間にかオブジェクト指向プログラミングフレームワークが出来上がっていた.

※これはLaTeXのコードです.

\usepackage{object}

\def\myclass#1{ % クラス定義(というかコンストラクタ)
  \def#1{}
  \object::field#1{foo}
  \object::method#1put(arg) {
    \par [\this.foo] and [\arg]
  }
  \object::method#1plus(lhs, rhs) {
    \par (\this.foo) $\lhs+\rhs$
  }
}

\myclass\obj
\myclass\obk
\obj.foo = {FOO}
\obk.foo = {foo}

\obj.put(BAR)
\obk.put(bar)

\obj.foo = {HOGE}
\obj.put(PIYO)

\obj.plus(x,3)
\obk.plus(y,z)
出力


経緯

LaTeX(というかTeX)では, コマンド(マクロ)の名前は一続きである必要があって, たとえば

\newcommand{\mycmd}#1{Do something on {#1}.}
\newcommand{\mycmd:subcmd}#1{Do something more on {#1}.}

みたいなことはできない.

TeX流にいけば

\def\mycmd#1{Do something on {#1}.}
\def\mycmd:subcmd#1{Do something more on {#1}.}

はできるけど

\mycmd{foo}
\mycmd:subcmd{bar}

とすると\mycmd{foo}でエラーになる.

なのでパッケージ作者は, 自分のパッケージで定義したコマンドには\mypackagemycmdsubcmdみたいな名前を付けることになって, 非常に見通しが悪い. これをなんとかするために,

\usepackage{subcommand}

\def\mycmd#1{Do something on {#1}.}
\subcommand\mycmd[:subcmd]#1{Do something more on {#1}.}

\mycmd{foo}         % Do something on foo.
\mycmd:subcmd{bar}  % Do something more on bar.

という感じに使えるsubcommand.styというパッケージを書いた.

これはなかなかうまくできたので, さて自分のパッケージを存分に作るか, と思っていたら, 「これってオブジェクト指向できるんじゃね?」と悪魔がささやき, 悪ノリしてたいへんなものをつくってしまった.

制限事項

  • 継承とかは無い(やればできそうだけど)
  • オブジェクトはグローバルにしか宣言できない(これはsubcommand.styの制限; そのうちなんとかするかも)
  • 基本的に(TeXが)動的束縛っぽい挙動なのであんまり激しく使うとハマるかも

しくみ

subcommand.styができてしまえばobject.styの方は朝飯前に書けたので, subcommand.styの説明. 基本的にはベースコマンド部分(\mycmd)までが実際のマクロで, そこからは1文字ずつ\@ifnextcharして, 定義済みのサブコマンドへ至るパスがある限りまた\@ifnextcharするマクロを返す. ようするにTrieを辿っている感じ. 次の文字を含めると定義済みのサブコマンドがない場合は葉に到達したことになるので, そこまでのパスが表すサブコマンドを実行する. あとは実装を参照. そんなに難しくないね! \expandafter! \expandafter! \expandafter!

Trie的なものができたということは, 連想配列ができたということなので, JavaScriptのオブジェクトが基本的にはただのハッシュテーブルであるように, オブジェクト指向してまおうという発想が自然と出てくる. object.styの実装はsubcommandを使うための構文糖衣に過ぎない.

追記: vs. 他のLaTeX OOP

id:zrbabblerさんに, PGF/TikZにOOPモジュールがあるということをトラックバックで教えてもらいました(「LaTeXでオブジェクト指向プログラミング」したもの - マクロツイーター). 他にやっている人はきっといると思ったので少し調べたりはしたのですが, これを見つけてはいませんでした. 面白い! せっかくなので, PGF/TikZでのOOP実現方法と僕のやり方の違い, 両者の長所・短所を比較しておきたいと思います.

PGF/TikZのooモジュールの実装(pgfmoduleoo.code.tex)を見ると, オブジェクトは\pgfoo@callerオブジェクトIDというものになっていることがわかります. オブジェクト.method(args)という記法が使える理由は, \pgfoo@callerの定義が

\def\pgfoo@caller#1.#2(#3){%
  ...
}

というふうになっているからです.

これを見て直ちにわかることは, たとえばオブジェクト.field = ...のような, オブジェクト部分を共有する他の記法を定義できず, それは即ち(構文論的にシンプルな)フィールド(ooモジュールの語彙ではattribute)のアクセッサが存在しないということです. 実際, PGF/TikZのマニュアルの67.6に

Attributes can be set and read only inside methods, it is not possible to do so using an object handle.

TikZ & PGF Manual Version 2.10 - 67.6 Attributes

と書いてあります.

これ自体はべつに不思議なことではなく, たとえばRubyもフィールド(Rubyの語彙ではインスタンス変数)にはインスタンス外部からはアクセスできません. Rubyでどうしているかと言えば, フィールド名と同名のメソッドフィールド名=という名前のメソッドをgetter/setterとして定義することで, (0引数のメソッド呼出しの括弧が省略できることにより)擬似的にふつうのフィールドアクセスを模倣しています. しかしPGF/TikZのooモジュールではこれが不可能なので, せいぜいJavaでよくやるようにgetter/setterメソッドを定義して括弧付きでアクセスするくらいしかできません. これが短所です.

PGF/TikZのooモジュールにも長所はあります. subcommandを使ったやり方よりもストレートにOOPを実現している分, 処理速度は大幅に速いはずです. subcommandを使ったやり方ではメンバアクセスの度にTrieを辿る処理が走るため非常に遅いはずです. 記法はどうあれ単にOOPしたいだけならPGF/TikZのooモジュールを使うべきでしょう. 逆にsubcommandとそれを使ったOOPは, 構文上のべんりさを極限まで追求しようとしたやり方と言えます.

広告を非表示にする