2006/05/28

Dexter社のWalkMocsという靴が気に入っていた。靴底がウレタンのような特殊な材質で、革も軟らかく、なにより出来がよくてはきやすい。結婚したころに恵比寿の三越で見つけて購入したもので、そのころには国内に正規代理店があったんだろう。
最近になって靴底がだめになってしまったので修理したいと思い、取扱店を探したんだけど、ない。どうやら日本での取扱がなくなってしまったようだ。ボーリングシューズだけはどっかのボーリング用品店が輸入しているっぽいけど。こんなことなら先週USにいったときに注意して探せばよかった。革はまだまだしっかりしているので、普通の靴底でもいいから何とか修理できないかなあ。
このメーカーの靴が自分の足に合うことは分かっているので、USのWebサイトから購入しちゃうか。WalkMocsというシリーズはなくなってしまったようだけど、このDiscoveryが近い製品みたい。

2006/05/27

パズル「九個の?」をSchemeで解く

かれこれ1年以上も解けていないパズルがあった。ペントミノの一種で、9個のクエスチョンマークを9×8のグリッドに詰めるというもの。以下のページでJavaアプレットが遊べる(はず)。

実は年末にも一回挑戦している。このときは結局、3日3晩かけても計算が終わらなかった。 解法として思い付くのは、グリッドにクエスチョンマークを置く組み合わせを総当たりで調べる方法のみなんだけど、各クエスチョンマークは上下左右裏表に自在におくことができるため、グリッドのどこかにクエスチョンマークを1つ配置するだけで実は208パターンにもなってしまう。そうすると、調べなければいけない組み合わせはペタオーダーになり、今のコンピュータでは一時的に保持することすら物理的に無理な大きさになる。Gaucheに用意されているcombination-for-eachを使えばいい具合に対処してくれるかもしれないと楽観したけど、やっぱりだめだった。ここまでは咋年末の話。

実際にはクエスチョンマークが安易に重なってしまうようなパターンがほとんどなので、それを無視しつつ組み合わせを求めるようにすれば計算量の爆発が抑えられそうなものだ。そのためのプロシージャは先日作った。というわけで、あらためてこの問題に対峙すべく年末のプログラムを書き直してみた。

; 9q-problem.scm
; 2006/5/26
;
; solver for the "9 questions" quize
; k16.shikano 

(use srfi-1)

(define row 9)
(define line 8)

;; question-mark
(define (face s n)
  (call/cc (lambda (break)
    (cond ((= n 1)
           (if (or (> (+ (remainder s row) 2) row) (> (+ (quotient s row) 6) line))
               (break '())
               (list s                (+ s 1)           ; **
                                      (+ s row 1)       ;  *
                     (+ s (* 2 row)) (+ s (* 2 row) 1)  ; **
                     (+ s (* 3 row))                    ; * 
                                                        ;   
                     (+ s (* 5 row)))))                 ; * 
          ((= n 2)
           (if (or (> (+ (remainder s row) 2) row) (> (+ (quotient s row) 6) line))
               (break '())
               (list s               (+ s 1)            ; **
                     (+ s row)                          ; * 
                     (+ s (* 2 row)) (+ s (* 2 row) 1)  ; **
                                     (+ s (* 3 row) 1)  ;  *
                                                        ;   
                                     (+ s (* 5 row) 1)  ;  *
                     )))
          ((= n 3)
           (if (or (> (+ (remainder s row) 2) row) (> (+ (quotient s row) 6) line))
               (break '())
               (list                 (+ s 1)            ;  *
                                                        ;   
                                     (+ s (* 2 row) 1)  ;  *
                     (+ s (* 3 row)) (+ s (* 3 row) 1)  ; **
                     (+ s (* 4 row))                    ; * 
                     (+ s (* 5 row)) (+ s (* 5 row) 1)  ; **
                     )))
          ((= n 4)
           (if (or (> (+ (remainder s row) 2) row) (> (+ (quotient s row) 6) line))
               (break '())
               (list s                                  ; * 
                                                        ;   
                     (+ s (* 2 row))                    ; * 
                     (+ s (* 3 row)) (+ s (* 3 row) 1)  ; **
                                     (+ s (* 4 row) 1)  ;  *
                     (+ s (* 5 row)) (+ s (* 5 row) 1)  ; **
                     )))
          ((= n 5)
           (if (or (> (+ (remainder s row) 6) row) (> (+ (quotient s row) 2) line))
               (break '())
               (list s (+ s 2) (+ s 3) (+ s 5)           ; * ** *
                     (+ s row) (+ s row 1) (+ s row 2)   ; ***
                     )))
          ((= n 6)
           (if (or (> (+ (remainder s row) 6) row) (> (+ (quotient s row) 2) line))
               (break '())
               (list s (+ s 2) (+ s 3) (+ s 5)           ; * ** *
                     (+ s row 3) (+ s row 4) (+ s row 5) ;    ***
                     )))
          ((= n 7)
           (if (or (> (+ (remainder s row) 6) row) (> (+ (quotient s row) 2) line))
               (break '()) 
               (list s (+ s 1) (+ s 2)                             ; ***
                     (+ s row) (+ s row 2) (+ s row 3) (+ s row 5) ; * ** * 
                     )))
          ((= n 8)
           (if (or (> (+ (remainder s row) 6) row) (> (+ (quotient s row) 2) line))
               (break '())
               (list (+ s 3) (+ s 4) (+ s 5)                       ;    ***
                     (+ s row) (+ s row 2) (+ s row 3) (+ s row 5) ; * ** *
                     )))))))

;; available question-mark faces
(define valid-face-list
  (filter (lambda (x) (not (null? x)))
    (let fs ((s 0))
      (if (> s (- (* row line) 1))
          '()
          (let fn ((n 1))
            (if (> n 8)
                (fs (+ s 1))
                (cons (face s n) (fn (+ n 1)))))))))

;; check if two lists are distinct with each other
(define (distinct? l1 l2)
  (= (length (lset-union eq? l1 l2))
     (+ (length l1) (length l2))))

(define (distinct-cdr ls)
  (let R ((tail (cdr ls)))
    (cond ((null? tail) '())
   ((not (distinct? (car ls) (car tail)))
    (R (cdr tail)))
   (else
    (cons (car tail) (R (cdr tail)))))))

(define (trim-combinations ls n proc)
  (cond ((> n (length ls))
         '())
        ((= n 1)
         (map list ls))
        ((> (- n 1) (length (proc ls)))
         (trim-combinations (cdr ls) n proc))
        (else
         (append
          (map (lambda (x) (cons (car ls) x))
               (trim-combinations (proc ls) (- n 1) proc))
          (trim-combinations (cdr ls) n proc)))))

(trim-combinations valid-face-list 9 distinct-cdr)

これを9q-problem.scmとして、シェルからtimeした結果。

[05:37:46] k16@debian:~/gauche $ time gosh 9q-problem.scm > 9q-result.txt 

real    272m22.609s
user    266m0.799s
sys     0m1.174s

約4時間半。その後、求める組み合わせについてmemoizeとかしてみたりもしたんだけど、実行時間は変わらない。おそらく何か間違ってるんだろう。

気になる結果は、互いに対称かもしれない解が全部で16通り得られた(→解答)。

2006/05/24

仕事でワシントンD.C.に行ってきた。無理やり時間をつくってスミソニアン航空宇宙博物館とナショナルギャラリーへ。どちらも市の中心部にあり、無料なので、打ち合わせのない時間に拝観することができた。ホワイトハウスとか見てませんから。興味ないのでどうでもいいんだけど。残念なのは、市内にはない航空宇宙博物館の別館にいけなかったことかなあ。

R0010059

R0010146

2006/05/15

Firefox 1.5でいつのまにか日本語の均等割り付けができるようになってるのにきがついた。

むかしは苦労したものだ。http://k16journal.blogspot.com/2005/05/htmlcss2.html

2006/05/14

LaTeX(TeX)で文字列中の文字を置換したい。

cf. Character substitution in TeX
http://hisashim.livejournal.com/276024.html

上記のページにほとんど目的の答えがあるので、これをもう少し汎用にしてみた。
\newcommand{\replacechar}[3]{{\wordbyword{#2}{#3}#1\end }}
\newcommand{\cutoff}[2]{{\relax}}
\def\wordbyword#1#2#3{\ifx#3\end \let\next=\cutoff
\else\ifx#3#1#2%
\else#3%
\fi \let\next=\wordbyword\fi \next{#1}{#2}}
\replacecharの第1引数に文字列、第2引数に置換前の文字、第3引数に置換後の文字を指定する。
いくつか制限がある。判明しているのは以下のとおり。
  • 文字列中のスペースは切り詰められる
  • ほかのコマンドの中で使うには\string\replacechar{foo}{o}{a}などとする必要がある
上記の2番目の制限は、どうやら\defの再帰がトップレベルでしか使えないためっぽい。
同じ理由で、\replacecharを入れ子にして使えない。つまり、文字列中の複数の文字を置換したかったら、同様な方法で新しいマクロを定義する必要がある。たとえば、索引の特殊文字をエスケープするコマンドは、次のようにして作れる。
追記:この例では「"」そのもののエスケープができないので注意。
\newcommand{\indexescape}[1]{{\indexwordbyword#1\end }}
\def\indexwordbyword#1{\ifx#1\end \let\next=\relax
\else\ifx#1{!}"!%
\else\ifx#1{@}"@%
\else\ifx#1{|}"|%
\else#1%
\fi\fi\fi \let\next=\indexwordbyword\fi \next}

\def\myindex#1{\index{\string\indexescape{#1}}}
\myindexの定義に\stringを使っているのも2番目の制限のため。

いちおう、実行例。

replacechar.tex
replacechar.pdf

2006/05/12

sumiiの日記 - callccによる排中律の証明
http://d.hatena.ne.jp/sumii/20060507/1147006438

さらに劣化コピー。Schemeのcall/ccで悪魔の契約書を作ってみる。型についてはまったく無知なので、排中律の証明とは関係ありません。
(昨日は会社でhisashimさんに適当な説明をした気がする。ごめんなさい。)

追記:最初のは間違ってたので差し替えました。
(define able-to-pay? #f)
(define remind-contract #f)

(define (devils-contract init-choice)
(let ((ability able-to-pay?))
((lambda (choice)
(cond ((equal? 'first-answer choice)
(display ""))
((and (equal? 'B choice) ability)
(display "Say any wish!"))
(else
(display "Devil gives you one billion dollars"))))
(call/cc
(lambda (continuation)
(cond ((equal? 'A init-choice)
(display "Devil chooses A"))
((equal? 'B init-choice)
(display "Devil chooses B")))
(set! remind-contract continuation)
(continuation 'first-answer))))))
この挿話は、悪魔にとって選択肢が(A)と(B)しかありえないことがポイントなんだと思う。つまり、(B)じゃなければ(A)。(B)でもないし(A)でもないってことはありえないドライな世界。上のコードだと、最初のcondが契約書に相当するつもり。
で、悪魔は自分が「発話した内容」なんて覚えちゃいない。上のコードでいうと、call/ccの中のcondは覚えちゃいない。あくまでも契約書オンリーなので、後から1億ドル払っても、(B)じゃなかったんだから(A)を履行するってことなんだと思う。

くだんのシナリオは、こんな感じ。
gosh> (devils-contract 'B)
Devil chooses B
gosh> (set! able-to-pay? #t)
#t
gosh> (remind-contract 'B)
Devil gives you one billion dollars

最初の(devils-contract 'B)で悪魔自身は「(B)を選びます」と言っているけど、人間が1億ドル払わなかった時点で実は(A)の契約が成立している。あとから1億円用意(set! able-to-pay? #t)してもだめ(悪魔の契約履行が10年後だったのがひっかけっぽいかも)。

1億円支払い可能な状態で契約し、悪魔が(B)を選択すれば、望みがかなえられるんでしょう。そんな人に悪魔が契約を持ち込むとは思えませんが……
gosh> (set! able-to-pay? #t)
#t
gosh> (devils-contract 'B)
Devil chooses B
gosh> (remind-contract 'B)
Say any wish!

2006/05/10

去年の秋から超朝方を心がけているのだけど、それが可能な体調の期間と困難な体調の期間(ねむ期)がある。ねむ期には生産性のピークが20:00過ぎに訪れるが、それでも非ねむ期における早朝の生産性には程遠い。
ここ一ヶ月にわたって長いねむ期が続いている。ねむ期から抜け出す方法が知りたい。ねむ期→非ねむ期の切り替えは休日の直後に訪れることが経験的にわかっていて、ということは休めばいいのか……

2006/05/08

枝刈りをしながら組み合わせを求めたい。つまり、似たような要素を同一視して、集合の総数を絞りながら組み合わせを求めていきたい。

ふつうなら集合をユニークな要素だけで構成しなおしてから組み合わせを求めればいいんだけど、「ユニーク」の意味によってはそれができない場合もある。
たとえばベクトルからなる集合で、「いずれかの項が同じであれば同一視」のような場合。次のような2次元ベクトルからなる集合Aがあるとして、
A = {(1, 2) (2, 3) (3, 4) (4, 5) (5, 6) (6, 7)}
集合Aからすべての項が異なる3つのベクトルを取り出す組み合わせは、
{(1, 2) (3, 4) (5, 6)}, 
{(1, 2) (3, 4) (6, 7)},
{(1, 2) (4, 5) (6, 7)},
{(2, 3) (4, 5) (6, 7)}
の4つになるだろう。この4つを求めるのに、すべての3要素の組み合わせを求めてから相異なる要素で構成されているものを取り出していると、集合が大きくなるにつれ厄介なことになるのが目に見えている。かといって、集合Aをあらかじめユニークな要素だけで再構成することもできない。

まず、ふつうの組み合わせを求める combinations プロシージャを次のような戦略で考える。

集合をリスト ls とみなし、その要素から n 個の組み合わせをすべて求めるプロシージャ (noraml-combinations ls n) を、次の 1.と 2.の append として作る。
  1. (car ls) と (noraml-combinations (cdr ls) (- n 1)) の cons
  2. (normal-combinations (cdr ls) n)
1.から「(car ls) を先頭の要素に持つ組み合わせ」がすべて得られ、2.で同様の操作をリスト全体にわたって行うつもり。ザ・リストの再帰処理。
(define (normal-combinations ls n)
(cond ((> n (length ls))
'())
((= n 1)
(map list ls))
((> n (+ 1 (length (cdr ls))))
(list ls))
(else
(append
(map (lambda (x) (cons (car ls) x))
(normal-combinations (cdr ls) (- n 1)))
(normal-combinations (cdr ls) n)))))
境界条件がなんか複雑で、本当にこれであってるのかよくわからないのはここだけの話。まあ、とりあえず意図通りの結果にはなるみたい。
gosh> test-set
((1 2) (2 3) (3 4) (4 5) (5 6) (6 7))
gosh> (normal-combinations test-set 3)
((#0=(1 2) #1=(2 3) #2=(3 4)) (#0# #1# #3=(4 5)) (#0# #1# #4=(5 6))
(#0# #1# #5=(6 7)) (#0# #2# #3#) (#0# #2# #4#) (#0# #2# #5#)
(#0# #3# #4#) (#0# #3# #5#) (#0# #4# #5#) (#1# #2# #3#)
(#1# #2# #4#) (#1# #2# #5#) (#1# #3# #4#) (#1# #3# #5#)
(#1# #4# #5#) (#2# #3# #4#) (#2# #3# #5#) (#2# #4# #5#)
(#3# #4# #5#))
枝刈りしながら組み合わせを求めるという本題を達成するには、枝刈りプロシージャ proc を渡して、1.の操作の cdr を proc に変えればいいだろう(2.の部分の cdr はリストの再帰的な操作のためのものなので、proc に変える必要はない)。
(define (trim-combinations ls n proc)
(cond ((> n (length ls))
'())
((= n 1)
(map list ls))
((> (- n 1) (length (proc ls)))
(trim-combinations (cdr ls) n proc))
(else
(append
(map (lambda (x) (cons (car ls) x))
(trim-combinations (proc ls) (- n 1) proc))
(trim-combinations (cdr ls) n proc)))))
枝刈りプロシージャとしては、ベクトルが相異なることを表現する distinct? と、リストとして表した集合から car と相異なる cdr を導く distinct-cdr 用意する。gauche の lset-union は、(use srfi-1) が必要だね。
(define (distinct? v1 v2)
(= (length (lset-union eq? v1 v2))
(+ (length v1) (length v2))))

(define (distinct-cdr ls)
(let R ((tail (cdr ls)))
(cond ((null? tail) '())
((not (distinct? (car ls) (car tail)))
(R (cdr tail)))
(else
(cons (car tail) (R (cdr tail)))))))
実行結果。
gosh> (trim-combinations test-set 3 distinct-cdr)
((#0=(1 2) #1=(3 4) (5 6)) (#0# #1# #2=(6 7)) (#0# #3=(4 5) #2#) ((2 3) #3# #2#))
しかしアレだな。lengthとか使いすぎなので、あまり効率よくないんじゃないか、これ。

2006/05/06

『RailsによるアジャイルWebアプリケーション開発』の制作方式

ここのところ『RailsによるアジャイルWebアプリケーション開発』の制作方式について意見を求められる機会が何回かあったので、もう半年も前のことだけどまとめてみる。

前提として、ふつうのコンピュータ書籍の制作過程には昨日定義した「後戻り困難ポイント」があり、それが原因で誰かが泣いている。誰も泣かないようにするには、昨日愚痴った「後戻り困難ポイント」を埋めてしまえばいい。それには、現在の手作業によるページレイアウトの工程(つまり組版工程)を放棄して、内容に責任を持つべき人間が印刷の直前までハンドリングできるようにすればいい。

『RailsによるアジャイルWebアプリケーション開発』の制作では、この方針を実現すべく、以下のような方法を採用した。
  • 印刷所に納品するデータはフォント埋め込みのPDF
  • そのPDFはLaTeXによる組版データから生成する
  • しかし原稿はLaTeXではないので、原稿をLaTeXに変換するスクリプトを用意する
  • 原稿はSubversionで版管理する
こうやって書き並べると単純すぎる話だけど、これらの方法を採用することによるメリットは強大だった。なにせ、著者(監訳者)や編集者がSubversionからチェックアウトした原稿(≠組版データ)を印刷の直前まで自分で修正でき、その結果が人手を介さずに印刷用の最終的な組版データになるんだから。ようするに、昨日の「後戻り困難ポイント」のうち、組版→編集→執筆の容易な還流が可能になる。あるいは、こういいかえてもいい。これまで組版の工程にかけていた数ヶ月の期間をゼロにできる!

たぶん、こんなのはソフトウェア開発では当たり前のことなんだと思う。ソースコード(原稿)を版管理し、それをデイリービルド(組版)しながら開発(制作)するってだけの話なんだから。ところが現在のコンピュータ書の制作では、コンパイルやビルドに相当する「原稿を組版にする作業」が完全に手作業なため、組版した後で判明した問題を原稿に戻って直したり、直して組版し直した結果をすぐに確認することができない†1

実現にあたっては障壁もいくつかあった。まず、原稿がLaTeXじゃない。また、仮にLaTeXであっても、それだけでは印刷所に安心してデータを渡すことはできない(印刷所の環境で同じようにコンパイルできる保障がない。そのため、印刷所の環境でコンパイルした出力を確認するという作業が不可欠になってしまう)。

印刷所に渡すデータの問題については、PDF/X-1aという業界標準があるので、それに準拠したPDFを生成できれば問題ない。これについては「Debianにotfパッケージをインストールして、dvipdfmxでOpenTypeフォントを埋め込んだPDFを吐き出すまで」を参照。

原稿がLaTeXじゃない問題については、まず原稿がどんな形式だったか示す必要があるだろう。こんな感じのテキストデータだった。
■H1■はじめに
■H2■本書の読み方
本書は初めてRailsに触れる人うんぬん……

★Rubyのコード
本書にはRubyのコードがたくさん出てきます。
しかも予約語は太字に、ダブルクォーテーション内はスラントにしなくちゃなりません。
◆→コード←◆ruby
puts "Hello World"
◆→ここまでコード←◆
ちなみに★で始まってる部分は小さな見出しつきの項目になります。

ここは、また本文に戻ってます。

● 箇条書きもあります。
こんなふうに、なんとなく箇条書きな部分がタブで示されています。
● もうひとつ箇条書き

また本文。

◆→コラム←◆Joeの疑問
枠で囲った記事もあります。「David曰く」「Joeの疑問」のほかに、何も指示のないコラムもあります。
◆→ここまでコラム←◆

1. 箇条書きとは別に
2. 連番もあります

■H3■もっと細かい本書の読み方
脚注もつけなければ◆→訳注 ←◆

■H2■終わりに
おしまい。
これは下訳をお願いした業者さんがよく使っている形式(というか編集指示)を拡張したものだけど、多かれ少なかれどこも似たような「マークアップ」を使っているようだ。見てわかるように、あくまでも人間が手作業で組版するためのコメントみたいな指示書き程度しか施されていない。コンピュータでそのまま処理するにはちょっと厄介な状態といえる(しかも、バリデータなんてないから、けっこういいかげん)。

このような「自然言語+簡易マークアップ」の構造を読み取って適切なLaTeXのスタイルに対応付けるためのフィルタを用意する必要がある。今回はゆるい規則を処理しなければいけないこともあって、後から柔軟にフィルタの仕様を変更できるようなGaucheスクリプトを正月にでっちあげた(私的に余暇を利用して作ったものなので、後日公開する予定。いま見返すと謎な処理が多いけど、なんとか目的の動作は実現している)。

上記の例の変換結果は以下のとおり。
\chapter{はじめに}%

\section{本書の読み方}%

本書は初めてRailsに触れる人うんぬん……%

%

\begin{entry}%
\item[Rubyのコード]
\item 本書にはRubyのコードがたくさん出てきます。
\item しかも予約語は太字に、ダブルクォーテーション内はスラントにしなくちゃなりません。
\begin{ruby}

puts \codesl{"Hello World"}
\end{ruby}%
\item ちなみに★で始まってる部分は小さな見出しつきの項目になります。%
\end{entry}%

%

ここは、また本文に戻ってます。%

%

\begin{myitemize}%
\item 箇条書きもあります。%
こんなふうに、なんとなく箇条書きな部分がタブで示されています。
\item もうひとつ箇条書き%%
\end{myitemize}%

%

また本文。%

%

\begin{column}{J}{の疑問}
枠で囲った記事もあります。「David曰く」「Joeの疑問」のほかに、何も指示のないコラムもあります。
\end{column}%

%

\begin{enumerate}%
\item 箇条書きとは別に%
\item 連番もあります%%
\end{enumerate}%

%

\subsection{もっと細かい本書の読み方}%

脚注もつけなければ\footnote{\kern-.5zw[訳注]}%

%

\section{終わりに}%

おしまい。
このLaTeXの出力から書籍にするには、さらに各要素についてスタイルを定義する必要がある。実はこの作業がいちばん大変なところなんだけど(LaTeXのいい加減な規則や拡張に起因)、これは会社の業務として作成したものなので今のところ公開できない。まあ、LaTeXの泥臭いところが満載なので、実際のところあまり面白くないし。

さて、ここまでは、この方法が従来の方法に比べて万能であることを意図的に強調してきたけれど、現実には適用できないプロジェクトが大半である。著者や制作業者の並々ならない協力が必要なこと、全員がアクセスできるサーバ環境が必要なこと、編集者に現状に対する問題意識が少ないこと、など原因はいろいろある。今回はとくに変換スクリプトをSchemeで作っちゃったりしたので、問題意識のある編集者であっても実際に使ってもらいにくい。

希望はある。こういった問題を解決して汎用性の高いツールにまとめあげる大仕事を身を挺して背負ってくれそうな人がいる。できる限りの協力はしようと思う。



もちろん例外はあって、最初から(商品として妥当な)レイアウトを含めて執筆された(執筆者のローカル環境以外でも同様にコンパイルが通る)LaTeXの原稿があれば、少なくとも執筆者は現在でも最後まで原稿を修正できる。ただし、編集者にLaTeXのスキルがなかったり、印刷所でコンパイルが通らなかったりして、必ずしもうまくいくとは限らない。

2006/05/05

ふつうのコンピュータ書籍がどうやってつくられているか。
  1. 企画
  2. 執筆や翻訳
  3. 編集
  4. 組版
  5. 印刷製本
おわり。

といいたいとこなんだけど、現実はこんなにシンプルじゃない。問題なのは、各ステップがほとんど分裂していること。
  • 編集してから執筆をやり直してもらうことは、よほどのことがない限りできない。
  • 組版してから編集をやり直すことは、実際には頻繁にやらざるをえないんだけど、とても手間と時間がかかる。
  • 書いてはみたけど時間が経ちすぎて市場性がいまいちになっちゃったね。でも企画からやり直すわけには……
  • 当然のことながら、印刷してから誤植が見つかっても直せない。
うー。

上にあげた4つの「後戻り困難ポイント」のうち、最後の1つは物理的にどうしょうもない。どんな製造業にも後戻りできない一歩っていうのがあって、書籍制作の場合にはそのひとつが印刷工程なんだと思う(流通するまでは本当の意味で後戻りできないわけじゃないんだけど)。

で、残りの3つの「後戻り困難ポイント」に直面した場合どうなるか。従来ありがちなのは、順に
  1. 読者が泣く
  2. 制作業者が泣く
  3. 担当者(i.e. 出版社)が泣く
  4. 著者が泣く
この順番に並ぶのは理由がある。まず、「後戻り困難ポイント」に直面するのは制作中の書籍の内容に十分な価値が認められない危惧が生じた場合なわけで、そのまま後戻りできずに流通までいたってしまった書籍の読者が泣く(このケースがほんとに多くていやになる)。
残りは読者が泣いていないケース。つまり、後戻りが不要だった(原稿が完璧で編集も完璧で組版も完璧)か、どっかの時点で困難を乗り越えて後戻りに成功した本である。「後戻りに成功」って書くと、プロジェクトX的な何かっぽくて聞こえがいいけれど、そんなわけはない。誰かがどこかで泣きながら困難な後戻りをやっている。誰がやっているかっていうと、出版社は著者に頭があがらないし、制作業者は出版社に頭があがらないから、推して知るべし。

すっかり愚痴っぽくなってるけど、ようするに現在の書籍制作は前近代的な「誰かが泣く」ソリューションに負っている。もちろん各工程で何も問題がおきなければいいんだけど、文筆のプロではない著者に本業の傍らで執筆してもらい(しかも半ば善意で)、それを一人の編集者が何本も同時にハンドリングし、安い単価でMacオペレータに手作業で組版してもらっている限り、デフォルトで誰かが泣いている。ということは、現在のやり方には何かしら問題がある。

どこに問題があるんだろう。考えられる可能性は、こんなとこ。
  • 紙の本が儲からなすぎ。何事も品質を求めれば金がかかるけど、金をかけようとしないので品質が悪くなり、読者が泣く。あるいは、何とか限られた金額で品質を高めようとして制作業者が泣く。編集者もサービス残業漬けになって泣く。
  • みんな紙が好きすぎ。とにかく紙に印刷されたものベースでしか制作が進まない。内容に責任を負うべき人間が制作にかかわる唯一の方法は、紙へ手作業で赤字を書き入れること。それをもとに、内容には関与しない人間が、やはり手作業でちまちまとデータを修正する。赤書きした本人が修正結果を確認できるのは数週間後だったりする。修正作業者が内容を理解しているわけではないので、その作業が新たな問題を生むことも少なくない。
  • 制作技術の進歩がなさすぎ。ページレイアウトを作るのに、いまだに植字工が活版を組んでいるのと原理的に同じ作業をコンピュータを使って手作業でやってる。一度組んでしまったものをスケジュールを崩さずに直すのは不可能。あとどうしょうもなくバカバカしいのは、節番号や図暗号の連番をふったり、ページ番号を参照したり、索引のページを解決したりするのが、全部手作業なこと。そんなのLaTeXやMS Wordでも自動でできるって(DTPソフトによってはできるものもあるけど、オペレータの数が限られていて一般には利用されていないというオチ。このへんも手作業な世界観が支配的で情けなくなるところ)。
というわけで、企画するまではともかく、そこから先の印刷直前までの工程は一種のバクチなのが実情。

ではどうするか。いちばんキモなのは、内容に責任を持つ人間が最終的なページレイアウトまでをハンドリングできるようにすること。紙に赤字で修正指示を出して……という他人任せな方法は極力回避する。これには2つメリットがある。
  • ありえないミスがなくなる。赤字の修正指示は字の汚い人間にとって苦痛なだけでなくリスキーでもある。それを見てデータを直す人間が内容を理解していれば対応できるけど、専門書でそんなことは期待できないわけで。とくに数式をよろしく対応してもらうのは絶望的
  • 制作期間を短縮できる。紙をやりとりするのは時間の無駄。
これを実現するには、「最終的なページレイアウト」とか、そういう夢見心地な発想をあきらめること。この発想の背景には、内容とレイアウトは別物という意識がある。たぶんこの意識はページレイアウトをする側のものだと思う。確かに本当に凝ったレイアウトを扱うにはそれだけを見る人間が必要だけど、そんなのは「伝票をチェックするためだけに人を雇う」というのと同じ発想で、それだけの規模がある業務なりプロジェクトなりでなければ無駄でしかない。(ちなみにここでレイアウトって言ってるのはデザインのことじゃない。)

で、ページレイアウトって、正直Webページ程度の表現力があれば事足りるものが多い。Webページであれば、公開する直前まで執筆してる人間が内容を修正し、それにCSSなりでスタイルを当てれば十分なコンテンツになる。しかも、執筆している人間に公開の直前まで許されている修正は、「てにおは」レベルのものじゃない。文章の階層構造はもちろん、説明の順番や図の配置まで、全部修正できる。どうして書籍の制作ではWebページのようにぎりぎりまで原稿を修正することができないのだろう。
Webブラウザと紙ではメディアとしての性格が違うという人も(DTPによる書籍制作にかかわってきた人のなかには)いるだろうけど、それは説得力がない。LaTeXとか、20年前からふつうに同じようなことができてたわけで、紙の制作に限って技術的な制限があるなんていうのはDTPソフト会社にだまされているだけだ。