golden-luckyの日記

ツイッターより長くなるやつ

gitで2つのリポジトリを混ぜる戦略を考える

「2つのgitリポジトリがあって、その片方をもう一方に取り込みたい」という状況を考えます。依存ライブラリのソースを自分のプロジェクトで保持したい、といった状況が典型的でしょう。

この場合、通常は git submodule を使うと思います。 git submodule であれば、他のプロジェクトを履歴ごと自分のソースの一部として管理できて、かつ双方の履歴をきれいに分離できます。

ただ、双方の履歴が分離できるということは、双方の履歴を混ぜられないということでもあります。そのため、 git submodule は、他のプロジェクトのソースに自プロジェクト独自の変更を加えて管理するといった用途には向かないように思います。ではどうすればいいだろうか、という試行錯誤の記録です。

文章で書くだけでは状況がよく見えないので、本稿では主に図で状況を示します。 なお、2つのリポジトリにおけるコミットの状況を図示していきますが、実際の作業はこれらをローカルへcloneして行うことになるので、適宜読み替えてください。

git submodule で取り込む

いま、自プロジェクトをgitリポジトリAで管理しており、そこにgitリポジトリBで管理されている他プロジェクトを取り込みたいとします。ここで、セオリー通りに git submodule を使い、AにBを取り込むとします。

$ git clone [AのURL]
$ git submodule add [BのURL] [取り込み先のディレクトリ]

このときの状況を図示するとこんな感じです。

f:id:golden-lucky:20200504141935p:plain

以降、図中の色と記号はこう読んでください。

  • gitリポジトリAでのコミット:白(ギリシア文字)
  • gitリポジトリBでのコミット:緑(英小文字)
  • もともとAで管理していたファイルへの変更:〇
  • もともとBで管理していたファイルへの変更:□

「Aのcloneにおけるsubmoruleへの変更をAのリモートのみに push 」はできない

ここで、submoduleとしたディレクトリ内で何かを変更してコミットし、その変更をリモートで共有したいとします。 submoduleへの変更を含めてpushする必要があるので、そのディレクトリ内で git push するか、 git push --recurse-submodules=on-demand を実行することになります。

このとき、このpushはAのみならずBにも適用されます。これをAのみに適用する方法はなさそうです。

f:id:golden-lucky:20200504145242p:plain

この挙動は、「双方の履歴が独立している」というsubmoduleの意図には合致していますが、Bに伝播させたくないA独自の変更をB由来のファイルに施したい場合には困ります。 そうしたケースに対する対策としては以下のような方法が考えられそうです。

  • Bで「A専用」のブランチを切ってそれをsubmoduleで取り込む
  • Bをフォークして「A専用B」のリポジトリを作る

ただ、Aがプライべートだったり、専用にカスタマイズしたBを使いたいAのようなプロジェクトのgitリポジトリが増えたりすると、これらの対策ではあまりうれしくありません。

「B由来のソースをAの内部で独自に管理する」とBの上流の変更が取り込めない

では、AではBの履歴管理とは完全に独立してB由来のソースを使うのはどうでしょうか? たとえば、先ほどの状況でBへのpushが起こってほしくないので、submoduleについては「ローカルにcloneしたリポジトリ」での履歴管理だけで我慢する、という手はありそうです。

しかし、これだと、当然のことながら「submoduleの内容に依存するAの変更」をリモートで共有できません。 また、下図のように、Bの変更をあとで取り込むこともできません。

f:id:golden-lucky:20200504150659p:plain

git subtree で履歴を完全に混ぜる

結局、やりたかったことに一番近い解決策は、「A内でのB由来のファイルに対する履歴をAの履歴と完全に混ぜる」になりそうです。 それを実現する方法はいくつか考えられます。

  • git subtree を使う
  • Bを取り込む専用のブランチを作り、その変更を主たるブランチに随時マージして使う(その際、混乱を避けるために git worktree を使うとよさそう)

やりたいことを図にするとこうなります。

f:id:golden-lucky:20200504151310p:plain

ここでは、上記を git subtree で実現する手順をメモしておきます。 git subtree については下記の記事を参考にしました。

まず、Aをcloneしたディレクトリ内で、Bをリモートとして追加します。 そのBを git subtree で指定のディレクトリにとってくる、というのが基本的な流れです。 gitコマンドとしては以下の2つを入力すればOK。

$ git remote add -f [B用のリモートの名前] [BのURL]
$ git subtree add --prefix [Bを取り込むディレクトリ] [B用のリモートの名前] [Bのブランチ] --squash

# あとはふつうにgit pushすればAのリモートのみに変更が反映される

もし仮に、ここで「Bにも変更を戻したい」場合には、 git subtree push というコマンドを使って「そのための差分」を作り、それをGitHubのPull Requestなどで上流に送ればいいようです。 しかし、Aの履歴が混ざっているので、うまくやらないとうまくいかなそう(やってないのでわかりません)。

一方、Bの上流の変更をA内のB由来のファイルに適用するのは簡単です。簡単といっても、もちろん衝突は覚悟しないといけませんが…。

$ git fetch [B用のリモートの名前] [Bのブランチ]
$ git subtree pull --prefix [Bを取り込んだディレクトリ] [B用のリモートの名前] [Bのブランチ] --squash

git worktree を使った方法もそのうち試してみようと思います。

謝辞

この状況を考えることになったきっかけは、下記のなにげないツイートでした。

このツイートにコメントをいただいた @anohana さん、@ymotongpoo さん(と某チャットで付き合っていただいたみなさん)、@soranoba さん、@YukiharuYABUKI さん、ありがとうございます。おかげで自分が本当は何をしたかったのか整理できました。とくに @soranoba さんには git subtree の存在を教えていただき本当に助かりました。

みんなが出版社になれるのか

「本の編集者は何をしているのか」を書こうとしていて、もう何か月もまとまらなので、とりあえず「出版社が何をしているのか」を書いておきます。

素朴な出版社のイメージ=中間搾取者

とくに出版業界に興味がない人からみた出版社のイメージって、おおむね「著者と読者の間にいるやつ」という感じだと思います。 絵にするとこんな感じ。 著者の立場からすると「本を出すときにお世話になる会社」で、読者の立場からすると「著者が書いた本を書店で買えるようにしてる会社」ですね。

f:id:golden-lucky:20200227161750p:plain

この絵のイメージで出版社を捉えると、「原稿から本を作るコストはかかるだろうけど、中間にいるだけで本の売り上げの大部分が懐に入るのか。やはり既得権益は強いな」という印象を抱くのではないでしょうか。 そんな中間者はインターネットの力で取り除いてしまうべきなのでは?

f:id:golden-lucky:20200227161802p:plain

とはいえ、個人で個人の読者にモノを売るのはインターネットだけではやはり面倒です。 そこで、「著者が原稿を個人の読者に売るためのサービス」がいくつも登場して広範囲に利用されています。 AmazonKindle ダイレクト・パブリッシングとかBooth、あるいは日本ではまだなじみが薄いLuluやLeanpubなどです。

以降、本記事では、これらをまとめて「個人出版支援サービス」と呼ぶことにしましょう。 これら個人出版支援サービスの多くでは、電子書籍であれば著者の取り分が80%とか90%とかに設定されているので、従来の出版社はほんとに中間搾取者だったんだなあという思いをあらためてかみしめられますね。

f:id:golden-lucky:20200227162553p:plain

個人出版サービスなら90%の取り分が出版社経由だと10%程度にしかならない」というのは、著者から見ると紛れもない現実です。 が、この差はほんとうに出版社による中間搾取の結果なんでしょうか?

出版社そのものは販売網ではない

ここでちょっと立ち止まって、個人出版支援サービスが著者に対して何を提供しているかを考えてみると、それは販売網としての機能だろうと思います。 著者が原稿を個人の読者に売りたいが、それは大変なので、そのプラットフォームを一手に引き受けるサービスというわけです。 原稿を本の整形してくれる機能がついていたりもしますが、それはおまけみたいなものでしょう。

従来の出版業界でこれと同じ機能を担っていたのは、出版社ではなく、取次と書店です。 その意味で、個人出版支援サービスが著者に提供するのは出版社の機能ではなく、取次や書店のそれです*1

ただし、従来の出版業界の販売網には「出版社だけしか利用できない」という事実上の制限があります。 逆にいうと出版社には、従来の出版業界の販売網を利用できる唯一の存在としての価値があります。 そこに、個人でも容易に使える新しい本の販売網として登場したのが個人出版支援サービスだといえるでしょう。 おかげで「著者の原稿を読者に届ける」ためだけに出版社を使う必要はなくなった、ともいえます。

販売網への窓口は出版社の機能のごく一部

著者が個人でも利用できる販売網がある状況で、わざわざ出版社から本を出すのは、一見するとバカげているようにも思えます。 確かに、「自分が書いたそのままを読者に届ける」ことが目的なら、そのために出版社を使う必要はべつにないかもしれません。 そういう人にとって出版社を使うのは割高です。

しかし出版社は、従来の販売網への窓口として読者と著者の間を取り持っているわけではありません。 出版社が「読者と著者の間を取り持っている」のは間違いないんですが、それは「著者」→「読者」という一本のラインの上で両者を仲介しているという意味ではなく、最終的な本のための原稿を著者と一緒になって作っているという意味です。 著者、出版社、読者のリアルな位置づけを絵にするとこんな感じです。

f:id:golden-lucky:20200227201907p:plain

さらに個人出版支援サービスの存在も考慮すると、こんな感じになります。

f:id:golden-lucky:20200227201921p:plain

出版社=売り物の本の専門家

先の絵を見てもらうと、「出版社は販売網としての役割をそもそも担っていない」という先の主張がよりクリアになると思います。 そして、「出版社は最終的な本のための原稿を著者と一緒になって作っている」という主張も、なんとなく伝わるのではないかと思います。 ほとんどのまともな出版社は、著者が書いた原稿の「てにをは」を直して組版して販売網に流すだけでありません。 「売り物の本」の専門家として、企画段階から著者との間でかなりのインタラクションを繰り返しながら、最終的な本の原稿を一緒に作り上げています。

この「売り物の本の専門家が本を作るのを手伝ってくれる」という機能は、私見では、出版社がもっとも価値を発揮できる部分です。と同時に、もっともコストがかかる部分でもあります。 しかし、「売り物の本」って、なんかふんわりした表現ですよね。 そんなふんわりした表現でしか説明できない微妙さがあるので、出版社の最大の武器として俎上に載せにくいなあと常々もどかしく思っていたりもします。 ぶっちゃけ、販売網の独占的利用という点に価値を示せなくなったいま、出版社の将来を左右するようなポイントであるとさえ思っているんですよね。 なのでもうちょっとうまく言語化したい。

まとめのようなもの

個人出版支援サービスのようなものとの対比で「従来の出版社は…」と感じるときがあったら、それはきっと出版社の使い方を間違っているかもしれません。 あるいは、売り物の本にするのを手伝う専門家として機能できていない出版社と付き合っているのかもしれません。

*1:念のため補足しておくと、これは取次や書店が中間搾取者だったという話ではありません。それぞれ、本という商品のディストリビューターとして、個人出版支援サービスと同じ10%~20%程度の取り分でずっとやってきているはずです。このへんが本というコンテンツのディストリビューターとしてビジネスをやっていくための妥当なラインなのかもしれません。

2020年 年賀状

2年ぶりにPostScriptしました。

github.com

コードを見てもわかりにくいかもだけど、「20をランダムに並べた配列を作り、 2020 という文字列が完成した場合にだけ色をつける」というロジックとしては単純なプログラムをPostScriptで手書きしています。

スタックに対する操作でプログラムを書くことで名高いPostScriptですが、データの格納にはarrayも使えて、これの操作にはわりとコツがいる、ということがわかりました(具体的には、put 操作をしても結果の配列がスタックに載らないので、 dupなどしておかないといけない)。

f:id:golden-lucky:20200101212945p:plain

出版社を作って4年が経ちました

ラムダノートという出版社を作って4年が経ちました。

https://cdn.shopify.com/s/files/1/1634/7169/files/132x320_240x.png

www.lambdanote.com

去年に引き続き、今年もちょっとふりかえりをしてみます。 この記事はラムダノートの技術 Advent Calendar 2019の25日めのエントリーです。

第4期(2018年12月~2019年11月)のふりかえり

『n月刊ラムダノート』はじめました

今年は『n月刊ラムダノート』という不定期刊行誌を3月に発行し始めました。 去年のふりかえりで第4期の目標として掲げていた「単発の本じゃない形で濃い技術情報をお届けする新企画」は、これのことです。

ぶっちゃけ、技術書、読むの大変じゃないですか? 正直なところ、作るほうもかなり大変です。 技術書に限らず、いま出版社が次々に新刊を出しているのは、半ば商売を維持するためという構造的な側面があります(それだけが理由ではないです)。 読む人も作る人もさまざまな無理感を抱いているなかで、それに飲まれるのはつらい。

そもそも、技術書を読んだり作ったりするというのは、そんな悲壮感が漂う行為ではなかったはずです。 実際、同じように読む人も作る人も大変という状況にあっても、その限界な状況をお互いが楽しめる場もあるのだと知りました。 いわゆる技術書典というやつです。 そこで感じた「こういうのでいいんだよこういうので」に対する技術書を作ってきた側としての試みが『n月刊ラムダノート』だとも言えます。

そもそも執筆してくれる人を募るところから不安でいっぱいでしたが、ありがたいことにエッジな方々から記事を寄せていただけて、1年めのVol.1は「No.3」まで出すことができました! こうやって一覧するだけでどれも読みたくなりますね。

Vol.1, No.3(ワインレッド)
  1. 代数的データ型とパターンマッチの基礎(κeen)
  2. パターンマッチ in Ruby(辻本和樹)
Vol.1, No.2(ミカンオレンジ)
  1. LISP 1.5の風景(川合史朗
  2. 計算機科学から見たディープラーニング(今井健男)
  3. Q#で始める量子プログラミング(田中孝佳)
Vol.1, No.1(セルリアンブルー)
  1. TCPの再送制御機構(西田佳史)
  2. 「コルーチン」とは何だったのか?(遠藤侑介)
  3. MLOps の歩き方(有賀康顕)

いまは次号に向けた準備をしています。 完全にぼくがボトルネックな状態になっており、すでに原稿を頂いている皆様には本当に申し訳ありませんが、引き続きよろしくお願いいたします 🙇‍♀️

とはいえ、企画が余っているという状態ではまったくないので、すでに執筆をお願いしている方、寄稿したら面白そうなネタがあるという方、寄稿させたい人がいるから紹介するという方、次の次の号あたりの掲載を目指してぜひご連絡ください。

www.lambdanote.com

なお、『n月刊ラムダノート』は現在のところ自社サイト直販のみ送料無料という販売方法をとっており、書店での販売はしていません。 が、これはテクニカルな問題によるもので、この販売方法にこだわっているというわけでもなく、もし取り扱いたいという書店さまがいたらご相談ください。 今期はいろいろ模索していきたいと思います。

あと、いちばん重要なことですが、紙版は在庫いっぱいなのでぜひお早めにご注文ください。 宅配便ではなくポスト投函ということもあって年末年始のお届けはもう無理なのですが、「お正月には電子版で読んで紙は年始に届いたのを会社にもっていく」とかどうでしょう!

技術書典7に落ちた

落ちるんですね…。 その日だけ半ば思いつきで電子版半額セールをしたのですが、予想を大きく超える数の注文を頂きました。本当にありがとうございました。 この規模の割引セールをする余裕はあまりないのですが、これを機に当社の存在を知ってもらえたような気もするので、率直にうれしかったです。

個人的には、セール中に技術書典7の運営のお手伝いをしていたので、サーバ(ShopifyでなくPDFを生成配信してるほう)が落ちないかはらはらだったよ…。

現金が足りなくなりそうになった

落選セールの売り上げがなかったらまじでまずかった。 綱渡りが続きます。

新刊は『プログラミングHaskell 第2版』

いろいろあって(主に訳者の @kazu_yamamoto さんのおかげ)、当社から「第2版」を出せました!

プログラミングHaskell 第2版(紙書籍+電子書籍)www.lambdanote.com

この本には個人的にはやはり思い入れがあり(前の会社で携わった本にはどれも思い入れがありますが)、とくに「改良したい」と思っていたところを改良できてよかったです。 完全に新しい本になっているので、旧版を持っている方にも価値があると思います(旧版の知識だけで現在のHaskellの型クラスを理解するのは厳しいと思う)。

ただ、『n月刊ラムダノート』をがんばっていたのもあり、第4期の書籍新刊はこの1タイトルのみでした。 このへんが経営的な課題としてあるのかもしれませんが、新刊をたくさん出さなくても生きのこる出版社を目指します。

第5期(2019年12月~)

そんなわけでラムダノートは第4期をなんとか生き延び、2019年12月から第5期に入りました。 年初にいきなり役員借入金(経営者から会社への資金貸し出し)が発生するらしい(白目)。 ただ、こちらのうかつなツイートに対してほんとにたくさんの応援を頂き、幸いにも必要な金額はだいぶ少額になりました。ありがとうございました!

最後に、来期は「いろいろな意味でうちでしか作れないであろう本」をいくつか発表できそうです。 お楽しみに!

直販サイトを作って書籍を売ること

昨日までこのアドベントカレンダーでは、PDFの内部の話から始めて、XMLという構造化文書の話、Pandocで記法を変換する話、EPUBで本というパッケージを作る話というように、徐々にレイヤを上げてきました。今日と明日はさらにレイヤを上げて、出版社の立場の話で締めくくろうと思います。

現在、日本の出版事業の中心は、「版元」「取次」「書店」という3者(いわゆる業界三者)が担っています。 メーカーと小売りの間に卸しがいるという構造は特別なものではありませんが、業界三者がちょっとだけ他と違うところがあるとしたら、書店と版元との柔軟な直接取引が少なく、取次-書店間、取次-版元間での委託取引が中心になっていることです。 この構造を支えているひとつの柱は再販価格維持制度による書籍の定価販売なんですが、この構造のおかげで、日本はかなり書店の数が多い国であり続けました。 2000年代初頭には全国で2万店くらいの本屋さんがあったようです(参考)。 米国の書店数も同じころにやはり2万店くらいだったらしいので(参考)、ほんとに本屋さんいっぱいあったんだなあ、という気持ちです。 いまは12000店を下回っているようなので、この20年で6割くらい、たぶん平成の間に1万店以上の書店が全国から消えたことになりそうです。

書店が少なくなったということは、当然、本を売る場所が減っています。 その一方で、新刊の発行点数は、同じ期間に毎年最大で3%ずつくらいの増加傾向にあります(参考)。 売る場所が少なくなったのに、新しく売るものの数が増えているということで、いろいろ嫌な想像が働いてしまいますよね。 ただ、少なくともこれだけはいえると思います。 読者の立場で考えたとき、「ふらっと書店に出かけてそこでたまたま1冊の本に巡り合う」といった機会は、じりじりと得難い貴重な体験になってきているのです。

そんな中で新しい出版社を立ち上げることにした当社では、書籍の主な販売チャネルとして「自社運用の直販サイト」を前提にした事業計画を立てています。 本当は取次さん経由で書店店頭でも買ってもらいやすくしたいとは常々思っているのですが、営業の専任がいないこともあって、この直販サイトで読者の皆さんから注文を頂く、つまり「小売業」が事業の中核です。

幸い、直販サイトを自分で立ち上げて小売業をするのは本当に簡単な時代になりました。 いろいろな選択肢があると思いますが、当社はShopifyを採用しました。 直販サイトをオープンした直後の2017年3月にカナダ大使館で「日本初のShopifyミートアップ」に参加した記憶があるので、ちょうどShopifyが日本市場に本腰を入れようとしていたころでもあったみたいです。 最近は山手線の車内でもShopifyのCMを見かけるので、ちょっとどきどきしています。

Shopifyのよいところ

Shopifyを採用した最大の理由は、APIが公開されていて自分でアプリケーションサーバを作れることです。 当社では、電子書籍を売りっぱなしにするのでなくアップデート版の継続提供などもしたいと考えていたのですが、そういう仕組みを最初から持っている小売サイトのためのプラットフォームはあまりありません。 そのため、販売サイトのガワのデザインだけでなく必要なアプリまで自分たちで作れるShopifyが魅力的でした。 むしろガワのデザインをへんに凝るつもりはなかったので、デフォルトでそれなりに満足できるのがありがたかったです。

あと、これは使ってから気づいたのですが、バックオフィスのための管理画面がものすごくよくできています。 いまはぼく自身が発注作業をすることはなくて、バックオフィス業務は共同経営者にお任せなのですが、管理画面が玄人向けすぎるとそうもいかなかったはずです。

さらに、Amazon PayやGoogle Payに早期から対応してきたこと(Apple Payも利用できるけどトラブルが多いので停止中)、手数料がお値打ちなPayment Gatewayが利用可能になったこと(実質Stripeで手数料が安いのはまじでありがたい)、コンビニ振込のKOMOJUに対応していて現金指向のお客さんもサポートできること、振込サイクルが1週間なこと、クレカ詐欺の検知機能(ちょっとシビアすぎる気もするけど)など、書籍のように単価が低い商品で小売をやっている当社のような立場にとって切実な点で地味にありがたい設計になっているのも助かっています。

Shopifyにがんばってほしいところ

いろいろあるといえばあるんですが、チェックアウト画面のデザインがたまに崩壊するようなので直ってほしい(チェックアウトまわりは欧州対応のために画面構成をユーザがいじれない)。あと、APIタイムアウトがWeb経由だと厳しい(ぼくだけが使うものはCLIでいいんだけど)。そういう細かい点くらいで、大きな不便は思いつかない気がします。

できればこのままWebの雰囲気に十分なじんでいる出店者にとって使い勝手がよいサービスであり続けてほしいです。

謝辞

これまで当社のサイトでお買い物をしていただけたすべての皆さま、物流をお願いしている明和流通システムの皆さま、本当にありがとうございます。これからもよろしくお願いします。

Shopifyがないとやっていけないので、ShopifyとShopifyを支えているRubyおよびRailsの開発者の皆さまには本当にありがとうございます。

最後に、当社の直販サイトの使い勝手やワーディングに何らかの良さがあったとしたら、立ち上げ前から今に至るまで折に触れて @miwa719 さんと @m_seki さんから有益なテストとフィードバックを頂いているおかげです。あのチームすごい。

TeXの脚注をなんとかする

この記事はTeX & LaTeX Advent Calendar 2019の24日目の記事です。23日めはwtsnさんの記事でした。25日めは☃さんの記事です。

今年は3年ぶりにTUGに参加してきました。 TUGというのはTeX User Groupのことであり、TeX界隈の開発者とアドバンストな利用者からなる世界的なコミュニティです。 年に1回、地球のどこかで集まっていて、それもまたTUGと呼ばれています。 2013年には東京でも開かれました。

なお、今年はKnuthも参加しています。前回の参加は10年くらい前のことで、そのときはiTeXというネタ発表をしたことでも話題になりました。 今年は完全に聴衆として参加していたのですが、それでも圧倒的な存在感が圧倒的でした。

日本からはわたしを含めて5人が参加し、それぞれ思い思いのKnuth体験をしたようです。 わたしは圧倒的に圧倒されて距離をとっていたのですが、気が付いたら寺田さんたちが一緒に写真を撮っており、それに混ざっていたら、ちょっとだけお話をする機会が得られました。

実は、以前からKnuth本人に直接尋ねてみたかったことがあったのです。 それは、「Knuthの奥様は本当に脚注が嫌いなんだろうか?」ということでした。 というのも、"The TeXbook"の15章の末尾には次のような引用があって、これがずっと気になっていたからです。

Don't use footnotes in your books, Don.
--- JILL KNUTH (1962)

わたしは、一読者として脚注が好きです。 脚注が多い本はよくない、という一般論は理解しているのですが、これまで自分が本を読みながら、その内容を理解するにあたっては、脚注の存在に何度も助けられてきました。 リニアに読んで理解できるように編集されているのがベストというのは、ほんとその通りだと思います。 が、そもそも世の中はリニアに説明しやすいことばかりではなく、それで脚注をなくすことに腐心するくらいなら、むしろ脚注を濫用するくらいのほうがまし、とさえ思うのです。 本文で説明を完結させるのが前提として、すっきり書き上げた本文に脚注が追加されて解説に厚みが出ることは、読者としては歓迎すべきことではないだろうかと。

で、せっかくなのでKnuthに「奥様はほんとに脚注が嫌いだったの?」と質問してみました。 いま考えると唐突な質問でしたが、すぐに15章の引用のことを思い出してもらえて、「あの引用は本当に彼女が言ったことなんだ。けど、もちろん冗談としてね」と教えてもらえました。 まあ、おおむね予想の範囲内の回答だったといえますが、冗談であるという言質を得ることはできて安心しました。

さらに、「脚注をTeXに実装したということは、自分でも脚注を使うつもりはあったんですよね?」とも聞いてみました。 すると、「いままで4000ページくらい書いてきて、4つだけ脚注を使ったよ」と自慢されました。 どの本で使ったのかは聞き損ねたのですが、日本に帰ってから持っている本をぱらぱら見たところ、TAOCP 2edのVol.2に1つだけ見つけました。 ほかにKnuthの脚注を見つけた人がいたら教えてください(TeXブックで脚注の例として紹介されてるやつらは除く)。

TeXの脚注は出力されない場合がありますね

さて、その脚注なんですが、TeXには確かに脚注のための仕組みが実装されています。 しかし、この仕組みではあらゆるところに脚注を配置できません。 典型的に問題になるのは次のようなケースです。

\vbox{abc\footnote*{def}.} % この脚注は消える
\bye

LaTeXでも同様です。

\documentclass{article}
\begin{document}
\vbox{abc\footnote{def}.} % この脚注は消える
\end{document}

消えないまでも、脚注として期待される場所、つまりページの最下部に脚注が出ないという制限がある場合もあります。 「 minipage で箱の最下部に出てしまう脚注をページの最下部に出したい」というのはLaTeX初心者あるあるですね。 最近だと、 tcolorbox で同様の問題に苦労している人もいると思います。 対症療法として「\footnotemark\footnotetext を使う」というテクニックを知っている人は少なくないと思いますが、そもそもなんでこんな仕様になっているのでしょうか?

TeXはなぜ脚注を期待どおりに扱えないのだろう

TeXの脚注がいろいろアレなのは、TeXがページ組み立ての際に脚注を「特別な行」として扱うことが根本的な原因です。 大事なところなので、少しだけだけ詳しく説明します。

TeXは本文を組むとき、まず段落をいい感じの見た目になるように行分割します。 それから、各行を「メイン垂直リスト」(MVL)という場所にいったん集めます。 このMVLに行を集める処理のことを「ページビルダ」と呼びます。

いま、ある行に \footnote{...} が出てきたとしましょう。 ページビルダは、その脚注の中身を「特別な行」とみなし、MVLでは該当する行の直下に追加します。

f:id:golden-lucky:20191223231657p:plain

ちょっと紛らわしいのですが、ページビルダは最終的なページを作るわけではありません。 最終的なページは、ページビルダがMVLに集めた行からさらに1ページ分の材料を配置することにより作り上げられます。 この作り上げられた最終的なページは \box255 という箱に入れられます。 また、その際に「特別な行」は「insert」という特別な箱に入れられます。 もともと脚注だったやつであれば、この特別な箱は \box\footins です。 最後にこれらの箱をDVIなりPDFなりの1ページとして出力するのが「出力ルーチン」(OTR)という仕組みです。

f:id:golden-lucky:20191223231712p:plain

ここでポイントになるのは、 \vbox の中に \footnote が出てきてもページビルダがそれを「特別な行」として扱えない、という事実です。 それどころか、ページビルダは \vbox の中を覗きません。 つまり、そもそも脚注があることに気付かないのです。 というわけで、 \vbox の中に出てくる \footnote は、かなり早い段階でTeXからは「不可視」の存在になっているのです。

脚注をMVL上で可視にすればよい

原因が見えれば対処の方法も考えられるというものです。 具体的には、不可視の脚注をページビルダから見えるようにしてMVL上に載せる方法を考えます。

とはいえ、ページビルダはどうやっても \vbox の中は見ません。 なんとかして \footnote の中身を \vbox の外に出して上げる必要があります。

それを手動でやる方法が \footnotetext テクニックです。 このテクニックで実際にやっていることは、「その \vbox が配置されるページ」と同じページの材料としてMVLに載る場所(つまり本文のどこか)を人間が判断し、そこに脚注の中身を置く、という手作業だったわけです。

同じことを自動的にやる方法もあります。 いったん \vbox の中身をトークンリストという場所に逃がしておき、それを \vbox の直下で復元する、というマクロを設置するのです。 具体的にはこういう仕掛けを書きます。

\newtoks\mftn
\def\mfootnote#1{%
  \footnotemark
  \edef\@tempa{\the\mftn\noexpand\footnotetext[\the\c@footnote]}%
  \global\mftn\expandafter{\@tempa{#1}}}%
\def\mfootnoteout{%
  \the\mftn
  \global\mftn{}}

\begin{...}
  \let\footnote\mfootnote
  ...
\end{...}
\mfootnoteout

ページ分割する箱にも脚注を入れたい

トークンリストによる方法は、tcolorboxminipage など、 \vbox と同等な他の箱の中に脚注を配置するときの手法としてもだいたいうまくいきます。 しかし、breakable オプション付きの tcolorboxframed などで、箱の途中にページ分割が起きる場合にはうまくいきません。 箱が複数のページに分かれてしまうので、「その箱が配置されるページ」と同じページの材料としてMVLに載せる、という戦略がそもそも成り立たないからです。

筆者は昨年のTeX & LaTeXアドベントカレンダーにおいて、この問題に対する解決のアプローチがLuaTeXで得られることを示しました。

ただ、昨年の時点では脚注の高さを計算する部分の作りこみが甘く、ページの高さが紙面のサイズを越えてしまう場合がかなりありました。 今年はそれを作り込んだので、それをTeX Conf 2019で発表することを予定していました。 また、昨年のアドベントカレンダーでは時間切れになって説明をまったく書かなかったので、今日の記事で書いたような話をひととおり話すつもりでいました。

以下の図は改良版の yafootnote による tcolorbox への脚注の実例です。入れ子を含むかなり複雑な状況にも対応できているのがわかると思います(ちなみに入れ子の内側を breakable にすることは tcolorbox の制限でできないはず)。

f:id:golden-lucky:20191224000256p:plain

yafootnoteの実装について(読まなくてもよい)

というわけで、最後にこの yafootnote が何をしているのかを雑に説明しておきます。ほとんど開発メモです。

yafootnote は、LuaTeXのいくつかのコールバックを利用することで「ページビルダがやっているTeXの脚注のメカニズムをMVL以外でも模倣する」ものです。 具体的には、下記のように各種のコールバックを利用します。

  • post_linebreak_filter コールバックを利用して、「脚注の中身を特別な行として該当する行の下に移動する」
  • vpack_filter コールバックを利用して、「MVLに載らない特別な行の高さをゼロにする」
  • buildpage_filter コールバックを利用して、 「tcolorbox で分割される場合にページの高さを必要なだけ減らす」
  • pre_output_filter コールバックを利用して、「MVLの特別な行を \box\footins に移動する」

post_linebreak_filter コールバックで脚注を「特別な行」としてMVLに載せる際には、LuaTeXの「属性」の仕組みを使っています。 TeXマクロで脚注のコマンドを定義するとき、脚注の中身を個別のボックスに入れて、そのボックスに対して特別な属性を設定するという使い方です。 この属性を他のコールバックでも引っ掛けることで、ページビルダに任せずに脚注の材料を \box\footins に移動するようにしています。

buildpage_filter コールバックの使い道がちょっとわかりにくいんですが、これは、 tcolorbox が「ボックスの材料を \vsplit する際の高さ」を計算する際に、それを「脚注の高さ」分だけ減らすという処理に利用しています。 これを計算するためには tcolorbox/tcbbreakable.code.tex の一部にも改造が必要でした。 この「脚注の高さ」は、 tcolorbox による分割ではない通常のページ分割でも考慮する必要があり、それは出力ルーチンでやるしかないので、出力ルーチンにも改造が必要になります。

さらに、ページビルダの動作には、TeXの処理のさまざまなタイミングで非同期的に発生するという罠があります。 そのため、 tcolorbox による分割まで考慮した「脚注の高さ」がbuildpage_filter コールバック経由で計算され尽くすのは、実際にはページが完成した後になってしまいます。 これはいかんともしようがないので、実際の組版ではLuaTeXを2回実行することにしました。 1回めの実行では、各ページで最終的に必要になる「脚注の高さ」をすべて計算し、それを外部ファイルにいったん記録します。 そして2回めの実行で、その情報を使って実際のページを組み立てます。

なお、この手法はFrank MittelbachによるLuaTeXによるフロートの最適配置の研究からヒントを得ました。

まとめ

脚注大好き。

XMLからEPUBを作る

昨日までの話をふりかえってみます。

  • 構造化文書というと、どうしてもXMLタグの書式で構造を示すあの世界観が想起されやすい
  • しかし、書式はあくまでも記法にすぎないと思うことにして、構造のほうだけ抽象データ型でかっちり用意するという世界観もありうる。これはPandocやDocutilsでいちおう成功を見ている

このアドベントカレンダーでは、後者の世界観を「ライトウェイト構造化文書」と呼んでみました。 特にPandocの成功については、代数的データ型、パーサコンビネータ、パターンマッチを兼ね備えたHaskellに依拠する部分が大きかったのではないかなと個人的には思っています。

今日は、そんなHaskellEPUBを作ろう、という話です。 PandocにはEPUB Writerもありますが、痒い所に手が届く感じではなかったので、自分で生成器を作ることにしました。

HaskellXMLを扱う

EPUBの中身はXMLです。したがって、EPUBを作るにはXMLを扱う仕組みが必要です。

ここで、Pandoc構造のような抽象データ型とXMLの関係にちょっと思いを馳せてみます。 抽象データ型をXML的に観れば、あるセマンティクスをひとつ固定してその構造を代数的データ型で表したもの、といえます。 ということは、XMLを抽象データ型の観点で見れば、抽象データ型をさらに抽象したもの、といえるでしょう。 そのような対象を扱う手段として、HaskellにはArrowという仕組みがあります。 そして、このArrowを利用してXMLを扱うライブラリとして、HXTというものが使えます。

HXTそのものについては、時間の都合上、この記事では説明を省略します。 かなり昔に書いた記事がいまでも役に立つと思うので、使ってみようと思う人はそちらを参考にしてください。

HXTを使ってXMLからEPUBを作るアプリケーションを作りました

PandocにはEPUB Writerがあるので、たとえばMarkdownの原稿からEPUBを作るなら、素直に pandoc コマンドを使えば十分です。 しかし、野良XMLからEPUBへの変換でPandocを使うのは、個人的にはあまりメリットがないと考えています。 入力もXMLであるような場面で、わざわざその構造をPandoc構造にいったん潰してしまうのは、あまりうまくないからです。 原稿がXMLっぽいもの(HTMLを含む)なら、その構造を目いっぱい使ってEPUBを作りたいものです。

っていうふうに書くと、「EPUBの中身はXMLなのだから、元のXMLをそのまま使うだけではないのか」と思う人がいるかもしれません。 しかし、それだけだとふつうはEPUBにはなりません。 確かに、データとして見れば、EPUBは「XMLをZIPしたもの」です。 しかし、コンテンツとして見ると、Webブラウザなどで閲覧する前提で書かれたXML(あるいはHTML)と書籍として公開する前提のEPUBとでは、いろいろ異なる点があります。 そのためちょこちょこ細工が必要になるのです。

実際にEPUBを自分で作ってみると、面倒なのはこの辺の細工だなというのがわかると思います。

  1. ヘッダとか図のキャプションに文字列や連番を追加する
  2. そのヘッダに付加した連番で、本文から章や図を参照する
  3. 論理目次と物理目次をはじめとするメタ情報を作成する

1つめの話は、たとえば<h1>要素の頭には「第1章」、その下の<h2>要素の頭には「1.1」、図には「図1.1」などの文字列を連接するということです。 そんなのCSSでやればいいじゃん、と思うかもしれませんが、CSScontentプロパティが使える広義のEPUBリーダー(Kindleも含む)は一部なので、これはコンテンツのHTMLのほうに埋め込む必要がある、というのがEPUB作成者の間では一般的な認識だと思います。 また、そうやって生成した連番のテキストは、場合によってはそれを参照している側にも付加する必要があり、これが上記でいうと2つめの項目です。 これらの作業には、当然、(テキストではなく)XMLに対する操作が必要になります。

3つめの話は、読者が目に見える部分だけじゃなくて、そもそもEPUBの仕様的に必要なメタ情報がいっぱいあります。 詳しくは他の資料などを参照してもらうとして、とくにめんどくさいのはOPFファイルと呼ばれるものの準備です。 このファイルに、中で使われている画像から何からすべての情報をきちんと登録しなければ、正しいEPUBファイルになりません。 そのための情報は元のXMLソースから抽出してくることになり、これにもXMLの操作が必要になります。

先にもちらっとふれましたが、元のXMLをいったんPandoc構造に落としてもかまわなければ、Pandocがこのへんの面倒をすべてみてくれます。 Pandoc構造にはHTMLを生で保持するデータ構造もあるので、それで十分なことも多いでしょう。

が、せっかくなのでその辺の処理もいちど自分で経験してみるかと思って、HXTでXMLからEPUBを生成するアプリケーションを開発しました。 仕事でEPUBを生成する必要があるとき、いまでは基本的にこれを使っています(中身がわかっているので手をいれやすい)。

ちなみに「qnda」という名前の由来は画面をひっくり返すとわかると思います。

このqndaを使って、「WikipediaのエントリをEPUB化するWebサーバ」をScottyというHaskellの軽量Webフレームワークを使って書いたこともあります。 HTMLではなくMediaWikiをソースにしているので、qndaのもともとのコンセプトは完全に失われていますが…

複数のエントリ(デフォルトでは3つまで)をフォームに指定してSubmitすると、それぞれを章とするEPUBファイルが返ってくるような仕掛けです。

f:id:golden-lucky:20191223182625p:plain

生成されたEPUBはリーダーでこんな感じに読めます。

f:id:golden-lucky:20191223182656p:plain

EPUB、売ってないじゃん

というわけでHTMLに変換可能な原稿からは技術的にEPUBを自力で生成できる当社ですが、いまのところEPUBの商品は販売していません。 Amazon Kindleでも販売していないんですが、これには次のような理由(というか言い訳)があります。

現状、EPUBが真につらいのは、生成することよりも、リーダーごとの動作検証だと思います。 PDFでさえ特定のビューワーが思いもよらない動作をする場合があるのですが(とくにApple系)、これにEPUBリーダーでの動作検証をして販売できるモノを準備する余裕がありません。 また、もしモノは用意できたとしても、管理する商品形態が1つ増えるのは当社の体力的につらく、そのために残念ながら見送っているのが実情です。 EPUBも欲しいという声があるのは承知しているのですが…。(そもそもEPUBに未来はあるのでしょうか…)

ちなみにKindleも事情は同じで、生成はできても、販売するために解決しないといけないバックオフィス上の課題がいくつかあります。

以上、ドキュメント屋さん的にはいろいろ面白いEPUBだけど出版社的にはちょっぴり難しい判断がある、という話でした。

明日の記事の予定はまだ決まっていません。