2016/02/29

バイナリ化したデータ構造を外部のファイルに保存して、コンパイル時にHaskellソースに埋め込む

Haskellには連想配列のように使えるMapというデータ構造(Data.Map)があります。 実行中にキーと値を更新するつもりがなく、コンパイル時に生成される固定的なMapを実行時に毎回使うという場合には、 リテラルのリストをソースコードに書いてMapに変換して使うのですが、 リストのサイズが1万要素とかになるとソース全体が肥大化してコンパイルに無駄な時間がかかるようになります。 生のリストをコンパイルのたびになめる必要はないのだから、Mapに変換したデータを辞書のように外部に持っておいて、 それをソースから読み込んで使う方法があるに違いありません。(ようするに、RubyでいうMarshal、PythonでいうPickleがしたい、ということです。)

他のファイルの内容をHaskellソースに埋め込むには、一般にTemplate Haskellが必要です。 ありがたいことに、外部のファイルの内容をソースコードに埋め込む機能に特化したfile-embedという便利なパッケージが用意されているので、これをTemplate Haskellと一緒に利用してこの問題を解決してみました。

バイナリの辞書を作る

まず、外部に保持するバイナリ辞書を下記の手順で用意します。

  1. リストからMapを作る
  2. そのMapをバイナリ化する
  3. さらにGzipで圧縮
  4. 結果を別ファイルに書き出す

コードにするとこんな感じ。このコードはバイナリの辞書(Map)を生成するだけなので、コンパイルと実行はきっかり一回だけです。

mkdict.hs

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Lazy.UTF8 as BSLU
import qualified Data.Map as Map
import Codec.Compression.GZip (compress)
import Data.Binary (encode)

dictList :: [(Int, BSLU.ByteString)]
dictList =
  zip [1..] $
      (map BSLU.fromString ["西住", "武部", "秋山", "五十鈴", "冷泉", ...その他大勢 ]

main = do
  BSL.putStr $ compress . encode . Map.fromList $ dictList
  return ()

ここでのポイントは、Mapのもとになる連想リストに日本語などの多バイト文字列がある場合、Data.TextではなくData.ByteString.Lazy.UTF8を使うことです。 なぜData.Textが使えないかというと、バイナリ化に使うData.BinaryData.Textに対応していないためです。 また、Data.ByteString.Lazy.Char8ではバイト列がぶった切られて日本語の文字が壊れてしまうので、結果的にData.ByteString.Lazy.UTF8を使うしかありません。(ほかによい方法があったら教えてください)

バイナリ辞書をソースに埋め込んで使う

上記で生成したバイナリ辞書をdict.datのような名前でどこかに保存したら、 下記のようにしてHaskellソースに埋め込んで使います。

  1. embedFile関数にファイルを指定すると、Template HaskellのQモナドにくるまれたデータが返ってくるので、これを$(...)で取り出す(というかソースファイル中に接ぎ木する)
  2. 接ぎ木されるデータは正格なByteStringなので、fromChunksで遅延ByteStringに変換する(fromChunksはByteStringのリストをとるので、リストの文脈に入れるためにreturnしている)
  3. 圧縮された状態を解凍
  4. バイナリをほどく

getmessage.hs

{-# LANGUAGE TemplateHaskell #-}

import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Lazy.UTF8 as BSLU
import qualified Data.Map as Map
import Data.FileEmbed (embedFile)
import Codec.Compression.GZip (decompress)
import Data.Binary (decode)

dictMap :: Map.Map Int BSLU.ByteString
dictMap = decode . decompress $ BSL.fromChunks . return $ $(embedFile "data.dat")

getMember id = putStrLn $ case Map.lookup id dictMap of
  Just x -> BSL.toString x
  Nothing -> error $ "No entry for " ++ id

実行結果

$ ghc mkdict.hs -o mkdict
[1 of 1] Compiling Main             ( mkdict.hs, mkdict.o )
Linking mkdict ...
$ ./mkdict > data.dat
$ ghci
GHCi, version 7.10.3: http://www.haskell.org/ghc/  :? for help
Prelude>  :l getmember.hs
[1 of 1] Compiling Main             ( getmember.hs, interpreted )
Ok, modules loaded: Main.
*Main> getMember 3
秋山
*Main> getMember 5
冷泉
*Main>

参考資料

Compiling in constants(Haskell Wiki)
Gzipしたバイナリデータのリテラル表現をソースに埋め込んでいる事例(そんな人間が見ても意味がないデータをソースに貼り付けるのはいやだ)。 バイナリ辞書をCに変換して利用する例も紹介されているけど。
Haskellでバイナリデータ(yuga/gist:8255552)
Data.Textに対応したバイナリライブラリがなさそうなんだなということが分かりました。
How to compile a resource into a binary in Haskell?(Stack Overflow)
file-embedパッケージの存在を知りました。

2016/02/21

ターミナルモードのEmacsでHaskellを書いているときに補完候補をポップアップしてくれるcompany-ghc

Haskellのコードを書くときは、Windows 10からTeratermでDebianサーバにSSH接続し、そこでEmacsを-nwで起動して使っています。 標準のhaskell-modeでとくに不満はないのですが、いちおうghc-modも入れていて、単純なエラーの確認にはとても重宝しています。 でも、ghc-modの補完機能はほとんど使っていませんでした。補完のためのコマンドが手になじまず、調べている間に手打ちしてしてしまうので、ちっとも身に付かなかったからです。

とはいっても、ある程度の量を書いていると、やはり手で打っているのではしんどくなってきます。何とかならないかなーと思っていたところ、company-ghcというものを発見しました。

Company GHC
https://github.com/iquiw/company-ghc

ターミナルで開いているEmacsでも、統合開発環境のような補完メニューを実現するcompany-modeというツールがあって、そのHaskell版をghc-modを介して実現するためのパッケージらしい。試しにインストールしてみたところ、こんなふうに、まるで統合開発環境のようになりました。これならぼくにも使えそう。

インストールと設定の概略

すでにghc-modを入れているなら、そのインストール時にEmacsのパッケージをMELPAから取得できる状態にしていると思います。その場合は、M-x package-installcompany-ghcを指定するだけで、必要なパッケージを含めてインストールしてくれます(したがって事前にcompany-modeを個別にインストールする必要もありません)。MELPAからパッケージを取得する設定にしていない場合は、init.elかどこかに以下を設定します。

(require 'package)
(add-to-list 'package-archives
  '("melpa-stable" . "http://stable.melpa.org/packages/") t)
(package-initialize)

無事にインストールできたら、まずはcompany-modeを有効にします。haskell-mode利用時のみ有効にすることも可能ですが、せっかくなのでグローバルに有効にしておきます。そのほうがCtrl-c Ctrl-lでGHCiを起動したときにも補完が効くようになるし、いろいろ便利なはず。

(require 'company)
(global-company-mode)
あとは下記のようにcompany-ghcを設定すれば完了です。
(add-to-list 'company-backends 'company-ghc)
(custom-set-variables '(company-ghc-show-info t))

これだけでもとりあえず動くはずですが、company-modeのパラメータを少しいじったほうが使いやすそう。

(setq company-idle-delay 0.5)          ; 補完候補を表示するまでの待ち時間
(setq company-minimum-prefix-length 2) ; 補完候補の表示を開始する入力文字数
(setq company-selection-wrap-around t) ; 補完候補のスクロールが末尾に到達したら先頭に戻る

さらに、補完候補がポップアップ表示される際の背景色などを好みで設定します。下記の設定に加えて、Teratermの[ウィンドウの設定]で「256色モード(xterm形式)」にチェックが入っていて、かつサーバ側の環境変数TERMxterm-256colorに設定されていないと、とても日常の使用には耐えない悲惨なユーザインタフェースになってしまいます。

(set-face-attribute 'company-tooltip nil                  ; ポップアップ全体
  :foreground "black" :background "ivory1")
(set-face-attribute 'company-tooltip-common nil           ; 入力中の文字列と一致する部分
  :foreground "black" :background "ivory1")
(set-face-attribute 'company-tooltip-selection nil        ; 選択項目の全体
  :foreground "ivory1" :background "DodgerBlue4")
(set-face-attribute 'company-tooltip-common-selection nil ; 選択項目のうち入力中の文字列と一致する部分
  :foreground "ivory1" :background "DodgerBlue4")
(set-face-attribute 'company-preview-common nil           ; 候補が1つしかなくて自動で入力される場合
  :background "black" :foreground "yellow" :underline t)
(set-face-attribute 'company-scrollbar-fg nil             ; ポップアップのスクロールバー
  :background "Midnight Blue")
(set-face-attribute 'company-scrollbar-bg nil             ; ポップアップのスクロールバー
  :background "ivory1")

これで、最初の例のように、いい感じに補完候補が表示されるようになりました。

ちなみに、上記の色の設定例で「候補が1つしかなくて自動で入力される場合」とあるのは、下記のように候補が決定的になった状況のことです。

項目を選択した状態で[F1]を押すと、その項目のヘルプも見られます。

関係ないけど、TeXも捗る。

2016/02/09

LaTeXで連番リストに丸数字(\ajMaru)を使えたはずだけどhyperrefで困る話

LaTeXで丸数字の連番リスト環境の基本

そもそもLaTeXで丸数字を使いたいときは、otfパッケージajmacros.styの機能を使うのが一番お手軽です。 このスタイルでは、数値を指定してさまざまな囲み数字のグリフ(あれば)を呼び出すという便利な機能が提供されています。 たとえば、ふつうの丸数字であれば、\ajMaru{数値}として出力できます。

\ajMaru{1}、\ajMaru{100}、\ajMaru{101}

一方、enumerate環境でラベルのスタイルを変えたいときは、\theenumiとか\theenumiiといったコマンドを再定義します。 \theenumi\theenumiiは、それぞれ「enumi」や「enumii」という名前のカウンタに対応した「アラビア数字」に展開されるコマンドです。 その結果がenumerate環境でラベルを表示するのに使われているので、これらのコマンドの挙動を変えることで別の種類の数字を出力できるというわけです。 たとえばこんなふうにすれば、ラベルが大文字のローマ数字であるような連番環境が作れます。

\renewcommand{\theenumi}{\Roman{enumi}}
\begin{enumerate}
  \item 最初\label{A}
  \item\label{B}
\end{enumerate}
最初は\ref{A}、次は\ref{B}。

以上の知見を組み合わせれば、こんなふうにして丸数字の連番環境が作れそうです。

\renewcommand{\theenumi}{\ajMaru{enumi}}

しかしこれはうまくいきません。\ajMaruの引数は数値でなければならず、enumiはあくまでもカウンタの名前だからです。

ではどうすればいいかというと、ajmacros.styに用意されている\ajLabelというコマンドを使います。 このコマンドは、後続の\ajMaru{enumi}の中身を実際のカウンタ値として取り出してくれるように定義されています。

\renewcommand{\theenumi}{\ajLabel\ajMaru{enumi}}
\begin{enumerate}
  \item 最初\label{A}
  \item\label{B}
\end{enumerate}
最初は\ref{A}、次は\ref{B}。

hyperrefと一緒に使えなくなっているっぽい(2016年2月現在)

本当ならこれで話は終わりのはずなんですが、相互参照をクリッカブルにしてくれるhyperrefパッケージを同時に使うと話がややこしくなります。 hyperrefは、相互参照をクリッカブルにするためにカウンタ関連の機能をいろいろ勝手にオーバーライドするという鬼仕様なのですが、 ajmacros.styではそれに振り回されないように、 hyperrefを読み込んだ場合には\begin{document}の時点で\ajLabelなどをhyperref用にカスタマイズし直してくれます。

しかし、さらに厄介なことに、hyperrefはときどき内部の振る舞いをドラスティックに変更します。 そのため、せっかく再定義されている\ajLabelがまた使えなくなるという事態が2016年2月現在では発生しているようです。 具体的には、以下のような例で、上記のような事情を理解していないとちょっと追跡しにくいエラーが起きます。

\documentclass[dvipdfmx]{jsarticle}
\usepackage{otf}
\renewcommand{\theenumi}{\ajLabel\ajMaru{enumi}}
\usepackage{hyperref} % hyperrefを使う
\begin{document}
\begin{enumerate}
  \item 最初\label{A}
  \item\label{B}
\end{enumerate}
最初は\ref{A}、次は\ref{B}。
\end{document}

実行例

...
! Undefined control sequence.
\ajLabel ...abel \@arabic \else \Hy@ReturnAfterFi
                                                  \hyperref@ajLabel #1\fi {
l.17  \item 最
              初\label{A}
?

エラーが起きている\Hy@ReturnAfterFiは、かつてはhyperref.sty内で定義されて使われていたようですが、現在のhyperrefでは使われていないため、このような事態になってしまうようです。hyperrefパッケージのSVNのログを見ると、どうやら2011年4月に消えたようですね。

[hyperref-svn] oberdiek: r1291 - trunk
http://mail.gnu.org.ua/mailman/listarchive/hyperref-svn/2011-04/msg00002.html

上記のログを見る限り、oberdiekバンドルのltxcmdsパッケージで定義されている\ltx@ReturnAfterFiを使うようにしたので、\Hy@ReturnAfterFiをディスコンにしたようです。 \ajLabelの再定義では、この\Hy@ReturnAfterFiを利用しているので、上記の例がうまく処理できない結果になっているようです。

そこで、\Hy@ReturnAfterFi\ltx@ReturnAfterFiに変更するだけの以下のパッチをajmacros.sty(1.7b6)に当てて試したところ、意図した挙動に戻りました。 (\Hy@ReturnAfterFi\ltx@ReturnAfterFiの定義は同一なので当然といえば当然なのですが。。)

--- ajmacros.sty.org    2016-02-09 16:16:17.700785982 +0900
+++ ajmacros.sty        2016-02-09 16:15:57.548561902 +0900

@@ -685,7 +685,7 @@
 %      \def\check@UTF##1##2##3{\ifx\UTF##1\0x##2\else##3\fi}}{}}
 \gdef\ajRedefine@ajCommands{\@ifpackageloaded{hyperref}{%
        \let\hyperref@ajLabel\ajLabel
-       \def\ajLabel##1##{\ifHy@pdfstring\Hy@ReturnAfterElseFi\hyperref@ajLabel\@arabic\else\Hy@ReturnAfterFi\hyperref@ajLabel##1\fi}%
+       \def\ajLabel##1##{\ifHy@pdfstring\Hy@ReturnAfterElseFi\hyperref@ajLabel\@arabic\else\ltx@ReturnAfterFi\hyperref@ajLabel##1\fi}%
        \ajRedefine@ajCommand\"${Lig"$}\"&{Lig"&}\!*{Lig>.}\ajLig{Lig}\ajPICT{PICT}\"({PICT}\ajVar{Var}\@nil\@nil
        \aj@Redefine@ajCommand!{{Maru}!|{KuroMaru}""{Kaku}"#{KuroKaku}!~{MaruKaku}"!{KuroMaruKaku}\@nil\@nil
        \def\!J##1!K{\ifHy@pdfstring(##1)\else\expandafter\ifx\csname ajLig(##1)\endcsname\relax\@ajnumber{##1}{Kakko}%

hyperrefは、電子書籍をLaTeXで制作するうえでは唯一無二のパッケージなのですが、ときどきこういう面倒が起きるのがつらい。。

まとめ

またhyperrefか。

2016/01/27

技術書編集者として「これはやられた!」2015年の本

技術書の年間ランキング的なものについて、編集者たちに「これはやられた!」と思う他社の本を候補として出させたら面白いのでは、という会話を小耳にはさみました。これはまたとないアマゾンアソシエイトの機会!ということで、勝手に自分の候補を上げてみます。

と思ったものの、新刊の技術書をそんなにたくさん読んでいないうえに、去年「これはやられた!」と思った本はいずれも技術書ではなく、どちらかというと数学書っぽい本ばかりでした。それでも、ジュンク堂池袋本店の「新春座談会 このコンピュータ書がすごい! 2015年版」で取り上げられた本ばかりだし、たぶん技術者が読む(べき)本としても妥当なはずです。

コンピュータは数学者になれるのか? -数学基礎論から証明とプログラムの理論へ-

いま自分の本棚を見返したら、この本の隣にたまたま『日本の著作権はなぜこんなに厳しいのか』が並んでいて、一瞬だけ姉妹書に見えました。疑問形タイトル、文芸系とかビジネス系の出版社だと割と普通に見かけるけど、自分で本を企画するときには扱い方がわからず避けてしまうので、そういう書名を使いこなせる編集者になりたいです。というか、たぶん疑問形タイトルって、「これは既成の考え方をなぞった本ではなく、なおかつ人類は読むべきである」ことについて相当な自信がないと付けられない書名な気がします。

この本の場合、コンピュータと数学者のことを「算術計算に秀でたモノ」と考えている人にとっては、確かに「既成の考え方をなぞった本」ではないのでしょう。しかし、コンピュータとは自分が考えたプログラムが本当に動くことを確かめるためのものであるという人や、数学者はコーヒーから証明を生成する仕事であると気づいている人にとっては、どこかで見聞きして知っていたり知らなかったりする話にぐさっと明快な筋道を通してくれる本でした。一般向けの数学の読み物で見かける話も含まれてますが、call/ccは背理法ありの古典論理で純粋関数型言語は直感主義論理に対応しているんだという第5章のクライマックスに至る話を素で楽しめるのは、実際にプログラムを書いている技術者の特権だろうと思います。

出版社は青土社です。選択公理の読み物として一部で有名な‟The Pea and the Sun: A Mathematical Paradox”(Leonard M. Wapner)の翻訳である『バナッハ=タルスキの逆説 豆と太陽は同じ大きさ?』をはじめ(原書しか読んでないので翻訳の出来は知らない)、意外に「え?」っていう理学系の本も出してくるし、割と好きです。「現代思想」とか「ユリイカ」でも思想的な文脈で現代数学の記事をたまに掲載しているし、そのうち計算機科学や機械学習の特集もあるに違ない。

圏論の歩き方

Haskellを使うのに圏論はいらないって言うけど、みんな圏論圏論って言ってるので、どうしたって圏論が気になりますよね。そんな人間を狙いうちする本が日本評論社から発売されました。タイトルどおり、圏論の名所をめぐるツアーガイドのような本です。いつか自分の足で行ってみたいけど余裕がなくて行けない場所へ行った気になれる本です。去年の夏に読んで、いまこうやって記事を書くつもりで内容を要約しようとしても、さっぱり具体的な言葉にできないんですが、そのへんもツアーガイドに似ています(実際、「地球の歩き方」なんかにしても、行く場所を決めるときに読んだ内容をあらためて説明しろと言われても言葉に詰まりそう)。

正直、この本のうち計算機に関する部分については、まさに自分でこんな本を作りたかったという気にさせてくれる本でした。この本が企画として面白いと思うのは、コンピュータ書でいえばフレームワークに対する「クックブック」のような本を、数学の一分野で形にしてしまった点です。数学書、コンピュータ書でいえば「コードの動作について説明しただけ」だったり「コード読めばわかるよね」だったり、そんな立ち位置の教科書が少なくない気がします。コンピュータ書の場合には、実行例を見せることで「どんな場面でどんなコードが必要なのか」を説明できたりしますが、それを数学書でやったのがこの本なのかなあと思いました。いわば「圏論の実行例」。あるいは「圏論のクックブック」。もし何らかの事情で圏論について本当に「わかる」必要があるなら、体系的に書かれた教科書を読みながら紙と鉛筆で勉強するのが近道なんですが、そうやって教科書の証明を読み進めるときの「実行例」だけが本として、しかも圏論みたいな分野で出たということで、自分のなかでは「これはやられた!」一冊でした。

あなたの知らない超絶技巧プログラミングの世界

自分が過去に企画にできなかった本が企画化され、しかも想像以上に出来がいいのだから、「これはやられた!」一冊として選ばざるをえない。発売当時、くやしさをばねに紹介記事を書いたので、そちらを読んでください。

2016/01/08

英数字文字列を任意位置で改行するTeXパッケージを使う話

技術系のドキュメントでは、長い連続した英数字からなる文字列を等幅フォントで組む必要に迫られることが多々あります。 本文中にそんな文字列が連続して出現する原稿を、字間が異常に空いたり版面をはみ出したりすることなく組版するためのベストプラクティスは、有識者の間でも意見が分かれるようです。 うまく自然な改行位置に収まるように原稿のほうに手をいれられればベストですが、政治的経済的な理由で困難だったり、そもそも解説内容からして無理筋という場合もあります(Javaの変数名とかJavaの変数名とか)。

よくあるのは、空白やハイフンや記号文字の直後に恣意的な改行位置を指定する、という方法でしょう。 変数名を英単語の組み合わせだとみなすなら、この方法が自然に思えます。 キャメルケースの大文字の手前を恣意的な改行位置にすることもありうるでしょう。

しかし、「英単語として有意味な改行位置」は、このような文字列にとって必ずしも適切な改行位置ではないはずです。 たとえば、空白とかハイフンで区切ってしまうと、文字列のその部分に文字通り空白やハイフンがあるのか、たまたま改行があって空白やハイフネーション記号が挿入されているのか、区別しにくくなります。 キャメルケースの大文字の手前で改行されていると、その位置に文字通りの空白が入る可能性が気になって仕事になりません。 もちろん読者として読むなら意味を考えて区別できるんですが、作り手側にいると「勘違いして読まれたらまずいのでは」という不安をぬぐえないということです。

で、最近は、任意の文字間で改行するという思い切ったルールにするほうが理に適っているのではないかと考えるようになりました。 つまり、英数字の文字列ではあるけど、日本語や中国語のように組むわけです。 任意の音節区切りで改行くらいのほうが穏当な気もしますが、Webページだとword-wrap: break-word;が指定されている例も見かけるし、これをTeXでやってみようというのが本題です。

結論から言うと、seqsplitというパッケージがあります。もともとはDNAなんかの塩基配列を組むためのパッケージで、TeX Liveにも入ってます。 つまり、acgtの4文字からなる長大な文字列をページに組むことを目的にしています。 これを以下のような感じに使うことで、任意の位置で改行可能なバージョンの\textttが実現できます(実行例

\usepackage{seqsplit}
\usepackage{fancyvrb}
\VerbatimFootnotes

\makeatletter
% \def\seqinsert{\ifmmode\allowbreak\else\hspace{0pt plus 0.02em}\fi
\def\wordwraptt{\begingroup\obeyspaces\do@codeline}
\def\do@codeline#1{\texttt{\seqsplit{#1\relax}\endgroup}}
\makeatother

\begin{document}

Wikipediaによると、Brain*uckでは
\wordwraptt{%
++++++++[>++++[>++>+++>+++>+<<<<-]%
>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>%
.<-.<.+++.------.--------.>>+.>++.%
}というHello World!プログラムが書けます。

\end{document}

fancyvrbパッケージを読み込んでいるのは、\VerbatimFootnotesを使うためです。 このコマンドが提供するような仕組みがないと、\wordwrapttを脚注に配したときにスペースがなくなってしまいます。

改良したい点として、事前に中身の文字列を数えてあまり短いものは改行しないようにするといった処理もしたほうがよさそう。 (\penaltyによる調整は試してみたけれどあまりうまくいかなかった。)

なお、上記のような異常ケースでない場合は、Listingsというパッケージを使って\lstset{breaklines=true, breakatwhitespace=true}という設定をしてあげることで、そこそこ妥当な結果になるかもしれません。

(追記)さっそく改良案をいただきました。

あながちネタでもないと思っていて、似たようなことは考えていました(さすがに雪だるまをつかう発想はなかったけれど)。 上の例のコードブロックではコメントアウトしてある\seqinsertの再定義部分を書き換えると、このようなことが可能になります。


宣伝

TechBoosterがC89で頒布した6点のラインナップでは、上記のような仕組みを使うことで、Re:VIEWからでは制御困難な長い変数名の折り返し調整をなんとかしてみました。 が、コマンド中のスペースの存在を根本的に見逃すという大変残念かつ申し訳ない失敗をしています。本当にすみません。 電子版はなおっているはずなので、本誌に挿入されているQRコードでぜひ電子版も入手してください><

TechBooster in C89 特設ページ

というわけで、いろいろ微妙なTeXマクロだけど(LaTeXのことは知らない)、冒頭のツイートで思い出したのでネタとして。

2015/12/29

2016年賀状

年賀状書いた。
%!PS
(ps3d.inc) run
<< /PageSize [285 420] >> setpagedevice

-70 0 100 translate3d
[0 0 690 2] set-eye
[2 1 0.5] -45 rotate3d

/setrandcolor { % def
    /r1 {rand 5 mod 3 div} def
    /r2 {rand 4 mod 4 div} def
    /r3 {rand 3 mod 4 div} def
    r1 r2 r3 setrgbcolor
} def

/DS-Chocolade findfont [140 0 0 400 0 0] makefont setfont

1 1 20 { % for
  newpath
  0 1 20 { % for
    /y exch def
    0 y 320 mul moveto
    (2016) true charpath
  } for
  2d-path-convert
  clip
  gsave

  newpath
  0 20 300 { % for
    /x exch def
    /w 20 def
    gsave
      x 0 0 moveto3d
      x w add 0 0 lineto3d
      x w add 6000 0 lineto3d
      x 6000 0 lineto3d
      x 0 0 lineto3d
      closepath
      setrandcolor
      fill
      grestore
  } for

  grestore
  0.7 0 0.1 setrgbcolor
  stroke
  showpage
} for

2016.pdf by Keiichiro Shikano

今年はBill CasselmanさんのPostScript用3Dライブラリ ps3d.inc を利用しました(参照)。 使っているフォントは DS Chocolade。 スターウォーズは関係ないんだからね。

ps3d.inc はとても読みやすいソースで、しかも開発者のBillさん自身が Mathematical Illustration という本でわりと丁寧にマニュアルっぽいものを書いてくれているので、PostScriptで3Dを描いてみたい人にはお勧めです。 この本自体も、PostScriptを書きながら初等幾何も学べるという、実に面白い内容です(年に一回しか開かないけど)。 ただ、概説より先の解説はちょっと端折り気味なので、結局はこのライブラリの技術的根拠になっている Jim Bulin's Corners 1 も参照するはめになったけどな! ちなみに、Jim Bulin's Corners の vol.1 は元同僚が翻訳書(アフィリエイト)を出したので日本語で読めるんです。すごい。

過去のやつ。

2015/12/24

Re:VIEWな原稿のためのTeXフォーマットを作ってみたよ

tl:dr
  • これはTeX & LaTeX Advent Calendar 2015の24日目の記事である
  • Re:VEIW、LaTeXでPDFを作るんなら、そもそもRubyもRe:VIEWそのものも不要じゃない?
  • そこでRe:VIEW記法を直でコンパイルするTeXを作ってみた

(ここから本編)

今年の自分の仕事を軽く振り返ってみたところ、前職での最後の出版物となった『エクストリームプログラミング』をRe:VIEWを使って制作したり、無職になってから声を書けていただいた同人サークルTechBoosterの書籍制作でRe:VIEWが圧倒的に利用されていたり(年末のコミケで新刊解説書も売ってるよ!)、これまでになくRe:VIEWとの因縁めいた縁を感じる一年だった気がします。そこで、なにかRe:VIEWに関連したネタで記事が書けたらいいなあと思いました。

というわけで、TeX & LaTeX Advent Calendar 2015のためのネタとして、Re:VIEWとTeXがもっと仲良くなれるかもしれない方法を考えてみました。TeX & LaTeX Advent Calendar 2015の昨日12月23日の担当はdoraTeXさん、明日最終日の担当はzr_tex8rさんです。

さて、Re:VIEWの話を書くと決めたはいいものの、今年のTeX & LaTeX Advent Calendarには「今さら人に聞けない、TeXのキホン」という重点テーマが設定されています。Re:VIEWがTeXのキホンとは思えない。そこで、とりあえず形だけでも重点テーマを消化すべく、まずはTeXのキホンのひとつといえるアクティブ文字について説明することにします。TeXのキホンに興味がない人は「2. Re:VIEW風のシンタックスで書けるTeXを作ってみた」まで読み飛ばしてかまいません。

アクティブ文字とは

TeXとかLaTeXには、俗にいうマクロとかコマンドと呼ばれるものがあるのはご存じですよね。 バックスラッシュとか円記号の後ろに英文字が並んでるやつです。 このマクロとかコマンドを使って、ページに出力する要素の構造を指定したり、繰り返し入力する手間を省いたり、特殊な記号を入力したり、装飾を施したり、絵を描いたりできるのでした。

このマクロやコマンド、ほとんどの場合は「\から始まる文字列」(専門用語ではコントロールシーケンス)という形式の名前で定義するんですが、実はもう一つ方法があって、それが本節のテーマであるアクティブ文字です。 ようするに、単なる文字がまるでマクロやコマンドのように定義済みの内容に展開される機能がTeXにはあって、それをアクティブ文字と呼んでいます。

アクティブ文字「~」(チルダ)

アクティブ文字の代表例は「~」(チルダ)です。 ふつうのTeX環境における「~」には、HTMLにおける&nbsp;と同じく、ノーブレークスペースという機能が割り当てられています。 つまり、改行されるのは嫌だけどアキが欲しいような場所に「~」を挿入することで、TeXの組版処理を制御できます。

See Figure 1.1.  % 「Figure」と「1」の間で改行される可能性がある

See Figure1.1.   % 「Figure」と「1」の間で改行されないが、アキもない

See Figure~1.1.  % 「Figure」と「1」の間で改行されず、アキも入る

この挙動が、実はTeXの実装でハードコードされている機能ではなく、次のような感じでアクティブ文字「~」として「定義」されているというわけです。

\catcode`\~=13
\def~{\leavevmode\nobreak\ }

\catcodeというのが文字の種類(カテゴリコードと呼ばれています)を変更するためのTeXプリミティブで、「13」がアクティブ文字のカテゴリコードです。\catcodeで文字のカテゴリコードを13に変更すると、通常のコントロールシーケンス(バックスラッシュで始まる文字列で作れるやつ)とまったく同じ要領で、\defを使って動作を定義できます。

好きな文字をアクティブ文字に変更してみよう

もちろん、自分で好きな文字をアクティブ文字に変更して、その動作を好きなように定義することも可能です。 たとえば、「*」という文字を「\item 」に展開できると楽ができそうですよね。

\begingroup
\catcode`\*=13
\def*{\item }

\begin{itemize}
* 箇条書きが、
* こんなに、
* 書きやすく!
\end{itemize}
\endgroup
あるいは、文中の「8」という文字をすべて雪だるまにすると楽しそうですね(scsnowmanパッケージマジ便利)。
She's the last of the V8s!

\begingroup
\catcode`\8=13
\def8{\scsnowman[hat=true,muffler=red,arms=true]}

V8! V8!

\endgroup

問題は、こんなふうに気軽に「*」や「8」の意味を変えてしまうと、「*」や「8」という文字を本来の意味で使えなくなってしまうことです。とくに「*」は、文章中で使えないだけならまだしも(それだけでも困る場面はあるでしょうが)、\section*{連番なしの節タイトル}のような一般的なコマンドで使えなくなってしまうのは困ります。 実際、上記の2例では、\begingroup\endgroupを使うことで、これらがアクティブ文字として有効になるスコープを制限しています。

そんなわけで、こんなに便利で楽しいアクティブ文字ですが、ふつうにTeXやLaTeXで文章を書くときに利用できるものは最初に紹介した「~」のみです。残念ですね。 (改行文字を段落の終わりとして再定義して特定の環境内で暗黙に利用しているようなケースはあります。)

Re:VIEW風のシンタックスで書けるTeXを作ってみた

アクティブ文字で、なにかもっと面白いことはできないでしょうか。

前の節では、「*」という文字を再定義して\itemのように使う方法を紹介しました。 よく考えるとこれは、Re:VIEW記法における箇条書きのシンタックスそのものです。 ひょっとしたら、アクティブ文字をさらに駆使することで、バックスラッシュと波カッコというTeXやLaTeXの記法(悪名高い)に縛られずに原稿を執筆できるところまで持っていけるのではないでしょうか。いや持っていけるに違いない。

シンプルな箇条書きの記法

長い前置きはこれくらいにして、ここからはさまざまなRe:VIEW記法をTeXで実装していくことにしましょう。 まずはRe:VIEWの箇条書き記法をTeX上で再現してみます。

新しい段落が「*」で始まったらitemize環境に入り、新しい段落が出てきたら抜けて、その間に出てくる「*」は問答無用で\itemにするが、それ以外の「*」はそのまま印字する、という動作なら、*をアクティブ文字にしてこんな感じに書けそうです。

\newif\if@itemize
\chardef\active=13
\begingroup
\catcode`\*=\active
\gdef*{%
  \ifvmode\@itemizetrue\begin{itemize}
    \everypar={\end{itemize}\@itemizefalse}
  \fi
  \itemize@to@eol}%
\endgroup

\def\itemize@to@eol{\begingroup\catcode`\^^M=12 \@@itemize@to@eol}
{\catcode`\^^M=12 \catcode`\^^I=5 %
 \gdef\@@itemize@to@eol#1^^M{\review@itemize@head#1 \endgroup}}
\def\review@itemize@head{\if@itemize\item \else*\fi}

TeXやLaTeXのシンタックスは捨てることにしたので、ほかのコマンドに「*」が出てくる心配はいりません。 冒頭で\catcode`\*=\activeとして実際に文書を作成してみましょう。

ただしこの実装だと、同じ行に「*」が二回出てくるとおかしなことになったり、あとに何かしら段落が存在しないと意図しない結果になったりします。 本当ならもっと作りこむべきなのでしょうが、そもそも軽量マークアップ言語なんて制作者の技術的な都合で仕様が決まるものなんだから、ここはそういう仕様ということにして先に進みます。

Re:VIEWのインラインコマンド

Re:VIEWには@<command>{content}という形式のインラインコマンドがあります。 これを同様にアクティブ文字を濫用して実装してみましょう。

\begingroup
\catcode`\@=\active

\gdef\codereview{code}
\gdef\bfreview{b}

\gdef@<#1>#2{%
  \def\reviewname{#1}
  \ifx\codereview\reviewname\texttt{#2}\fi
  \ifx\bfreview\reviewname\textbf{#2}\fi}
\endgroup

こちらはTeXの記法に近い(?)ので比較的安直に実装できます。 上記では等幅にするコマンド(@<code>{...})と太字にするコマンド(@<b>{...})だけを定義していますが、必要に応じて好きなものを追加してください。

章見出しや節見出し

Re:VIEWでは「=」で行を書き始めると個数に応じて階層的なヘッダになります。 これをTeXで実装しましょう。めんどくさいですが、処理自体は素直なものです。

\begingroup
\catcode`\==\active
\gdef=#1#2{\let\@tmpa#1\let\@tmpb#2%
  \ifvmode \@headertrue\inc@header@cnt
    \ifcat\noexpand=\noexpand#1 \inc@header@cnt\let\@tmpa\relax
      \ifcat\noexpand=\noexpand#2 \inc@header@cnt\let\@tmpb\relax
  \fi\fi\fi
  \eq@protected\@tmpa\@tmpb}
\endgroup

\newif\if@header
\newcount\header@cnt
\def\inc@header@cnt{\global\advance\header@cnt by 1}
\def\reset@header@cnt{\global\header@cnt=-1}

\reset@header@cnt
\def\eq@protected{\if@header\proc@header\else =\fi}
\def\proc@header{%
  \expandafter\gen@header\header@cnt}
\def\gen@header#1{\@headerfalse%
  \ifcase #1 \review@chapter
  \or        \expandafter\review@section
  \or        \expandafter\review@subsection
  \fi\reset@header@cnt}
\def\review@chapter#1\par{\edef\chapstring{#1}\chapter[]{\chapstring}}
\def\review@section#1\par{\edef\sectstring{#1}\section[]{\sectstring}}
\def\review@subsection#1\par{\edef\subsectstring{#1}\subsection[]{\subsectstring}}
現状だとページの柱や目次に章タイトルを出力できないなどの問題は残っていますが、とりあえずできたということにします。
見出し行があるだけでずいぶんマークアップ言語っぽくなりますね。

Re:VIEWの環境

Re:VIEWでは//foo{から//}のような書式で特殊なスタイルのための環境を設定できるようになっています。 これもLaTeXの環境の記法に似ているので簡単に実現できるかなと思ったんですが、そもそもTeXには真のパターンマッチがないので、ややトリッキーな仕掛けが必要でした。

\begingroup
\catcode`\/=\active
\gdef//#1#{\begingroup\catcode`\/=\active\def//{}
  \csname review@#1\endcsname}
\endgroup

\long\def\review@list#1{\begin{alltt}#1\end{alltt}\endgroup}
\long\def\review@quote#1{\begin{quote}#1\end{quote}\endgroup}
\long\def\review@read#1{\begin{quote}#1\end{quote}\endgroup}
\def\review@footnote#1{\footnote{#1}\endgroup}

ここでちょっと悩ましいのが、よく似た記法を持つ//footnoteのようなRe:VIEWコマンド(波カッコがない点に注意)の存在です。これにも対応するには本気でパーザを書くことになりそうなので、//footnoteコマンドでもコンテンツは波カッコで囲むということにします。 つまり、この時点でRe:VIEWとは互換性のないシンタックスになってしまっていますが、軽量マークアップ言語なんて制作者の技術的な都合で仕様が決まるものなんだから(ry

あとなんかallttの中の改行処理がうまくいってないとか、入れ子への対応が不十分とか、課題は残ってるけど深追いしない。

Re:VIEWっぽい原稿をデフォルトでロードするTeXプログラムを作る

これらの定義をプリアンブルに書いたりスタイルファイルに書いたりして、LaTeXの \begin{document} ... \end{document} の間で実行するだけでも面白いといえば面白いのですが、どうせなら、.reファイルから直接PDFを組版できる独立したコマンドがあると便利ですよね。 というわけで、上記のマクロたちをフォーマットとしてまとめておきました。

revtex: TeX with Re:VIEW syntax
https://github.com/k16shikano/revtex

いちおう補足しておくと、TeXの文脈でフォーマットといったら、initexを使ってダンプする(あるいはダンプした)ファイルのことです。 そのフォーマットをデフォルトで利用するrevtexという新しいTeXの実行ファイルを作ることで、Re:VIEWっぽい記法の原稿を直接コンパイルできるようにしようというわけです。

日本語を組版したいので、ベースにするのはuplatexコマンドとします。 uplatexの実行ファイルをrevtexという別名で用意し、上記のフォーマットをかませて使うように設定すれば、こんなふうにTeXで直接、ほぼRe:VIEW記法の原稿をコンパイルできるようになります。

$ ls -la /usr/bin/revtex # revtexはuplatexのシンボリックリンク
lrwxrwxrwx 1 root root 16 Dec 22 12:28 /usr/bin/revtex -> /usr/bin/uplatex
$ cat sample.re          # sample.reは(ほぼ)Re:VEIW記法のファイル
= はじめてのUNIX

== 実際すごい

重金属を含んだ酸性雨が降りしきるなかをものともせず、
@<b>{マグロツェッペリン}//footnote{巨大飛行船}が回遊する!

* ネオサイタマ
* マルノウチ・スゴイタカイビル

その頂から退廃的な高密度ネオン看板が瞬く地獄めいた都市の喧噪を見下ろす影がひとつ急降下した。コワイ!

== もっとすごい

「こんにちはニンジャ・スレイヤー=サン。」
そういって敵は姿を見せぬまま@<code>{UNIX}端末にログインした。

= インガオホー

こんにちはダークニンジャ=サン。ハイクを詠め!

//quote{
トチノキ、フユコ……
}

#@# fin
$ revtex sample.re       # ほぼRe:VIEWな原稿がTeXでコンパイルされる
This is e-upTeX, Version 3.14159265-p3.6-u1.20-141210-2.6 (utf8.uptex) (TeX Live 2015/Debian) (preloaded format=revtex)
 restricted \write18 enabled.

kpathsea: Running mktexfmt revtex.fmt
...

いうまでもありませんがRubyもRe:VIEWも不要です。最後にdvipdfmxが何か言ってますが、無視する方向で。 生成されたPDFを以下に張っておきます。

画期的ですね。

実は、少なくともひとつ、どうにも解決できないダサい制限があって、原稿ファイルの末尾にオリジナルのRe:VIEWではコメントとして無視される「#@# fin」という行を書いておく必要があります。 TeXは組版をするとき、\endというプリミティブで原稿ファイルの終端に到達したことを知るのですが、Re:VIEWにはそのための記法がないので、こうしてお茶を濁しているわけです。

(追記) と思い込んでいたら、e拡張には\everyeofなるそのまんまなプリミティブがあると教えてもらいました。 これを使うと、確かに最後の行が不要になるので、ひそかにあきらめていた行コメントも実現できました。(追記おわり)

免責と今後の展望

auxによる参照解決とか、エスケープとか、いろいろさぼってるので実用品ではありません。 また、これ以上Re:VIEW対応率をあげようとか、バグを直そうとか、そういう予定もたぶんありません。 むしろ、あらためてMarkdownっぽいのを実装するほうが面白そうかも。

参考資料

TeX by Topic
TeX芸には欠かせない説明不要の名著。日本語訳もあるが入手は困難。原著者のサイトで英文なら全部読める。
1TeXの実装
TeX芸の北極。文字「1」をアクティブ文字にすることで、文字通り「1」のみでTeXコンパイル可能な文書を作る。
xmltexの実装
XML文書をTeXでコンパイルするという、海外のTeX芸のひとつ。
Re:VIEWフォーマットのドキュメント
これ以外にもいろいろ隠しコマンドがあるらしい。