2011/12/31

OLYMPUS SH-21 がきた

年末感あふれる進行でへろへろになりながら会社の納会に遅れてったら、余興の景品で豪華賞品を獲得してしまいました。オリンパスのSH-21。なんか今年はずいぶんカメラづいてるなあ。レビューを書く約束をしたのでファーストインプレッションを。

景品

帰宅してすぐに箱から出し、インテリジェントオートで何枚か撮ってみましたが、背面の液晶でプレビューすると、やたらに明るく写ってるけど何となくしゃきっとしない絵。設定いじれば変わるかなと思って、プログラムオートでいじってみるものの、さっぱり勝手がわからない。オールインワンカメラなんて、自分ではまず買わないタイプなので、かなり戸惑います。まあでもふだん使っているパナソニックのコンデジよりモノとしては楽しいので、翌日子どもと散歩に出かけるときに持って行こうと決めました。

翌朝、つまり今朝、散歩に出る前にフォーカスを合わせるコツをさぐりながら撮ったのがこれ。なんというひどいボケだw。こういう写真を見ると、ソフトウェアでは得られずハードの質で左右される部分がまだまだあるんだと自分に言い聞かせてD90用にレンズを買う口実になります。まあ、これくらい寄れるとそれだけで単純に楽しくなってしまうので、気楽に撮り流していくことにしましょう。

PC300002

このカメラ、背面液晶がタッチパネルになってて、タッチした場所でAFロックするようにできます。でも、それなりに明るい場所なら、従来のコンデジの要領でシャッターボタン半押しから構図を決めて撮るほうが楽でした。タッチパネルでAF操作するのに慣れてないわけではなくて、HTC Desireでは便利に使っているんですが、このカメラはタッチパネルの感度がいまいちよろしくない気がします。それにつけてもAFは楽ちんだ。

昨晩はしゃっきりしないと感じた肝心の絵ですが、晴天下ではずいぶん印象が変わりました。相変わらずインテリジェントオートはいまいちですが、プログラムオートにしてホワイトバランスのみオートにしてやると、完全にシャッター押すだけでソフトウェア支援をバリバリに受けたぱきっとした絵を快適に撮れるようになりました。

山手線

PC300035

キリンキャンディー

PC300036

無駄に広角側があるのもスナップにはうれしい。

PC300021

ライドオンストライダー

でも、こういう写真はむずい。

PC300060

暗いシーンでも何も考えずにシャッター押すだけ。

仲町商店街

日暮里

おもしろ機能として、パノラマが撮れちゃう。これはかなり興奮しました。このためだけに、これを登山に持っていこうと思った。高さが720ピクセルなのでPCで見るとそれほど迫力ないけど、背面液晶ではアニメーションでプレビューしてくれるので、かなり「おおっ」となります。

谷中コミュニティセンター

あと連写が楽しいです。これも背面液晶ではアニメーションとしてプレビューでき、やはり異様に楽しい。flickrが中心の自分にとっては、しょせん静止画なのでハンドリングが楽なのもうれしいです。ハイビジョン動画とか、正直もてあます。あれってみんな、撮影したやつをどうやって保管してるんだろう。もちろんこのカメラも当たり前のようにハイビジョン動画撮れるのですが。

もうひとつ、3Dが取れる機能があって気になってるのですが、背面液晶は3D対応じゃないので、3Dモニターがないと楽しめないらしい。残念。



別の日に撮影したものを追加しときます。



科学未来館

unsignaled signal

EXIF 情報が見られないのばかりで申し訳ないです。Eye-Fi → iPad 経由で Flickr にアップしたら EXIF がなくなってしまった。

2011/12/11

2012年賀状

年賀状書いた。

%!
<< /PageSize [285 420] >> setpagedevice

/fun { % y=x^2/81 for x<9, y=(x-18)^2/81 for x>=9
  dup 9 le {dup mul 81 div} {18 sub dup mul 81 div} ifelse
} def

/colwidth 62 def % set column width
/xoffset 15 def
/linehfactor 1.3 def
/fontsize 170 def
/numfont {
    /Sixties % /Bookman-Demi or /Times-Bold are also good
} def

/setrandcolor { % def
  /m exch def
  /r1 {rand 10 mod 7 div 1 m fun sub mul m 9 eq {1 add} if} def
  /r2 {rand 10 mod 7 div 1 m fun sub mul 0 add} def
  /r3 {rand 10 mod 7 div 1 m fun sub mul 0 add} def
  r1 r2 r3 setrgbcolor
} def

/centeringchar { % def
  dup stringwidth pop
  colwidth exch sub 2 div 0 rmoveto
} def % remaining a character string on top of the opstack

xoffset 0 moveto

0 1 3 { % for
  /i exch def
  i colwidth mul 0 rmoveto
  [3 1 2 3] i get % map (+1) (2 0 1 2)
  /base exch def
  0 1 10 { % for
    /t exch def
    /num t base add 10 mod def
    /f {i 3 eq {12} {9} ifelse} def
    /h {t 9 mul fun 2 mul f add} def
    numfont findfont
      [fontsize 1 0 linehfactor h mul 1 0]
      makefont setfont
    t setrandcolor
    num 10 1 string cvrs
    currentpoint
      /y exch def
      /x exch def
    centeringchar
    true charpath gsave fill grestore
    0.5 setgray stroke
    x h y add moveto
  } for
  xoffset 0 moveto
} for

showpage

去年まではちゃんと印刷して写真をとっていたのだけど、今年は ps2pdf の出力を Scribd にアップするだけというていたらくです。

2012

2010年2011年と、ぐるぐる回す系のデザインが続いたので、今年は縦スクロールにしてみました。回す系より地味だけど、縦のセンターを合わせなければいけないので、カラム幅と文字幅をもとに座標を調整したり、座標のリセットをしたり、見た目から想像されるよりも手間がかかります。

ナンバリング風の演出は、最初は円周の割合でフォントの高さの変化率を決めてみたのだけど、どうにも格好がつかず、適当な二次曲線(上記の /fun )を使ったらなんかいい具合になりました。あと「2012」だけが目に飛び込むような配色に苦労した。

今回の作例で使ったフォントは Sixties というフリーフォントです。他の一般的なフォントを使う場合は、Bookman-Demi とか Times-Bold のようなひげのあるフォントがおすすめ。


ここから Ghostscript で TrueType フォントを使うときのメモを書くけれど、自分ではよくわかってない話なので、個人マシンで遊ぶときとか意外には参考にしないでください。

Web から入手したフリーの TrueType フォントを PostScript 遊びで使いたいとき、インタプリタが Acrobat なら Windows や Mac にフォントをインストールすれば勝手に使ってくれるけれど、Ghostscript ではそうはいかない。基本的な手順は、

  1. TrueType フォントの名前を調べる
  2. GS から見える場所に置く
  3. GS の Fontmap に登録(/usr/share/ghostscript/8.71/Resource/Init/ とかにあるやつ)
だけど、1つめの手順でつまずく。Font HOWTOとか読むと、こんな手が紹介されていた。
$ ttf2pt1 -GA -l latin1 Sixties.ttf - 2> /dev/null | grep FontName
FontName Sixties
フォントの置き場所もなかなか一筋縄ではいかなくて、Fontmap に絶対パスを書きさえすればどこに置いてもいいのかと思いきや、GS が見に行ってくれる場所(/usr/share/ghostscript/fonts/ とか)にないとだめっぽい。

Fontmap への登録も、システムワイドな Fontmap ファイルに直接書かないと有効にならないようで、今回は Sixties.ttf を /usr/share/ghostscript/8.71/Resource/Init/Fontmap にこんなふうに登録した。

%!
% See Fontmap.GS for the syntax of real Fontmap files.
%% Replace 1 (Fontmap.GS)
(Fontmap.GS) .runlibfile

(Sixties) (Sixties.ttf) ;

2011/11/03

TeX ユーザーの集い 2011 雑感

10月22日に催された「 TeX ユーザーの集い 2011」に参加しました。「ユーザーの集い」というゆるい名前にもかかわらず、主要パッケージメンテナが一同に会する円卓会議で標準的な日本語 TeX 環境に向けた議論がなされたり、 LuaTeX 日本語化の開発状況の報告があったり、単なるエンドユーザーの集まりとは思えない雰囲気で楽しかったです。

そんな雰囲気の中、自分は一人のエンドユーザーとして、パッケージメンテナの方々や開発者を含む「 TeX ユーザー」たちがどこを目指してるのかを考えてました。ざっくり言うと、こんな 2つのベクトルがあるような気がします。

  1. TeX は、数式や図を含む組版に使える便利なツールである
  2. TeX は、ドキュメントのための形式のひとつである

当日の発表も、どちらかに大別できそう。たとえば、インプレスさんや達人出版会、奥村先生の招待講演のうち美文書の話、それにオーム社の事例は、主に 1 の立場でしょう。 KeTpic も 1 ですよね。一方、村上真雄さんの招待講演や解析概論プロジェクト、 OpenOffice は、 2 という見方だと思います。 LuaTeX の開発や TeXLive の報告は、ひとによってどんな立場の話としても聞ける類の内容で、その意味では、発表者ではない参加者の立場もまた上記 2 つのどちらかに当てはめることができそうです。

自分が今年の「 TeX ユーザーの集い 2011」であらためて感じたのが、2 の立場の重要性でした。ドキュメントのあり方というか、そういうレイヤで TeX 環境をとらえるべきなのではなかろうかと思い直しました。

TeX 環境には、きれいな組版ができるという出力側の優れた機能と、テキスト形式であるという入力側の利便性、それにプログラミング可能という特徴があります。それらをうまく使うことで、ドキュメント構造とスタイルという 2 つの抽象を分離できます。「うまく使う」というのがポイントです。うまく使わないと、まるで分離できない。でもうまく使えば、 XML/CSS のような固定的な分離だけでなく、ユーザにとって都合のよい分離を設計することさえできるはず。プログラミング可能というのはそういうことです。

(だから、なにも原稿が XML である必要はないのです。とはいえ、現実には補助線もなく適切な分離を毎回設計してそれを守り抜くのは誰にでもできることではありません。漏れのない抽象化は至難。だから、原稿そのものは XML 風だったり Wiki 風だったりするテンプレートを使って書くというのも次善策でしょう。閑話休題)

こういう話、実は予稿集の「ごあいさつ」にもきちんと書いてありました。実行委員の方々が2の立場を意識しているからこそ、村上さんの招待講演に結びついたのだろうし、実行委員の皆様には本当にどうもありがとうございました。勝手なことを言うと、さらに武藤さんによる「 InDesign 自動組版の可能性と限界」とかあってもよかったかも。テキスト原稿から InDesign への変換ツールを使った商業出版は、自分もほかに知ってるし、けっこう事例がありそうですが、 TeX との比較とかできるのは武藤さんくらいではないかと。そういう、構造/スタイルをはじめとする抽象分離を意識したドキュメント全般の事例をもっと聞いてみたかったです。 LaTeX から EPUB への展開とか、 TeX ユーザーにとって直接役立つヒントも多いはず。(昨今の電子書籍ブームみると、これらが来年のネタの提案としてふさわしいかはちょっと疑問ですが。)

gdgdな内容ですが、だから雑感だといったでしょ。あと、山本さんと八登さんの発表はおもしろかったです。そういえば、上記 1, 2 のような立場だけでなく、おもちゃとしての TeX という路線もありますよね。

2011/10/24

コダクロームの自家現像(ただし白黒)

コダクローム(Kodachrome)というリバーサルフィルムがあります。というか、ありました。Kodak社ではコダクロームの製造を2009年6月22日に終了しています。だからいまはもう手に入らない。

製造中止そのものは、一部のファンには悲しいことだったのだろうけど、まあオールドメディアの終焉の一幕に過ぎません。コダクロームの本当の終了は、世界で最後の現像所が取り扱いを停止した2010年12月30日でした。これが「本当の終了」だったのは、コダクロームが自家現像のできないフィルムだからです。

現代のふつうのカラーフィルムは、フィルムそのものが発色の機能を備えています。だから、所定の薬品を使って一定の手順を踏めば、撮影した画像をカラーで取り出せます。そのへんの現像所はもちろん、おうちの台所でも現像ができるということです。ところがコダクロームは、フィルムそのものには発色の機能がなく、現像の過程で何段階か露光させることにより発色させるという特殊な方式でした(耳知識)。そのため、家庭はもちろん、そのへんの現像所でもコダクロームの現像はできません。だから2010年12月30日を最後に、それまで世界中で販売された未現像のコダクロームはただのゴミになってしまいました。ある種のDRMのようなものですね。人類はちっとも学習しない。

そんなゴミが、我が家からも一本発掘されました。とはいえ、古いカメラの中に入っていた撮影済みのフィルムを取り出したらコダクロームだった!という事情なので、個人的には安易にゴミ認定しかねます(どうせスナップではあるのですが)。必死になって調べてみたところ、どうやら英語圏では白黒ネガフィルムとしての自家現像にチャレンジしている人たちがいるようです。

というわけで、さっそく試してみます。

Kodachrome

プロセスは "Kodachrome in 2011 – Process as Black and White" というサイトのものを参考にしました。参考というより、まるまる利用。現像マニアというわけではないので、その程度が限界です。以下はあくまでも素人による結果報告なので、もし参考にされる場合は、撮影画像が抜けてしまう覚悟でお願いします。

水洗い
水道水(30°C だった)で攪拌しながら 3分。排水はかなり黄色くなります。
T-Max現像液で現像
標準希釈(1:4)、20°C で計 6分30秒。最初の 3分間を連続攪拌、50秒停止→10秒攪拌を 3セット。排水に 30秒
コダフィックスソリューションで定着
標準希釈(1:7)、28°C で計 8分。最初の1分を連続攪拌、50秒停止→10秒攪拌を6セット、30秒停止→排水30秒
水洗い
20°C の水で2分程度の攪拌後、水道水(30°C だった)からの流水で 15分
こそぐ
ちょっと何を言ってるのか分からないかもしれないが、アクリルたわしで感光剤のない面(つるつるのほう)をこすり、黒ずみのようなものをこそいだ。フィルムワイパーかければ取れるのかなと思ったら、けっこうしっかり付いてるので、感光剤を傷つけないように注意しながら、けっこうしっかり黒ずみをおとしてやる。勇気がいる。
ナニワカラーキットNの漂白定着液で処理
上記サイトによるとC-41の漂白をしたほうがヌケがよくなる、ということなので、いちおうやっておく。標準希釈で 1分攪拌し、すぐ排水。
ドライウェル
上記サイトではフォトフローを使っているけど、そんなものヨドバシカメラに売ってない
乾燥
子供が留守のあいだに

結果です。EPSON GT-X800 でスキャンしただけの状態。ソフトウェアでは手を加えていません。長いことカメラに入りっぱなしなのを考慮して、もっと現像時の水温を下げるべきだったと後悔してますが、何が映ってるかは判明したのですっきりしました。

11

8

4

出来上がりについては、わたしの現像が未熟なのをおいておいても、残念な感じだと思いました(ところどころある白点は、こそぎ切れかなった黒ずみと思われ)。今回のような事情でもない限り、わざわざ余っているコダクロームを使ってモノクロ撮影をするようなものでもなさそう。

最後にいいわけっぽく、このコダクロームが眠っていたオリンパスのペンFというカメラと、同じカメラによるT-MAX100による自家現像作例も張り付けておきます。10年以上前の香港のようすです。

14

15

16

Olympus Pen F with pancake

なんてすてきなレンズ!

2011/10/12

D90がきた

デジカメを買いました。それも、3年まえに発売され、すでにカタログ落ちしている Nikon D90 をいまさら購入です。まったくそんなつもりはなかったのですが、まる7年にわたって使い倒した D70 がいよいよデータ保存時に深刻なエラーを引き起こすようになって、サービスセンターに持ち込むつもりで Nikon のサイトをのぞいたところ、アウトレットで D90 のボディが 59,800 円で売り出していたのに引っかかってしまったのでした。でもまあ、D70 を修理するにしても2万円は出費を覚悟しなければならないし、買い換えるにしても D7000 はなんかちがうし、D90 なら当分は腐らないし、ニコンダイレクトで3年保証だというし。。

なにはともあれ、さっそく試し撮りです。被写体はいつもの FM-2 WILDCAT。下が以前とった D70。レンズはどちらも東独ツァイスのフレクトゴン35mm。

FM-2 WILDCAT 1/144 Sweet

DSC_0235

やっぱり画素数が D70 の二倍というだけで単純にバージョンアップ感ある(でも D70 のほうはなぜか画質が FINE じゃなかったっぽいなあ)。被写体は実はこんなに小さい。

FM-2 WILDCAT 1/144 Sweet

関係ないけど、久しぶりにSWEETのページをみたら二式水戦が新商品とな! パーツは2機分入っているのだろうし、これは二枚反転ペラに改造してオレンジに塗るしか。。

そして、おつかれさまの D70。問題の原因はわかっているので、そのうち自分で修理してから息子に与える予定。

D70 with Tamron 18mm-200mm

この D70 が最後に活躍したのが先週末の息子の運動会でした(閉会式で脈略なく担任でもない先生に抱擁されている図)。この運動会の写真をあやうく全部ロストしそうになったことで買い替えを決意したみたいなものなのだけど。chkdsk でも fsck でもどうにもならなくなってしまうと、さすがにねえ。

DSC_0088

2011/09/08

TeX でソート

ソートはソートでも単純なバブルソートです。バブルソートは、前から2つずつ要素を比べて大きいほうをどんどん後ろに送っていくという、かなり素朴な昇順並べ替えの方法です。Wikipedia にのっているアルゴリズムはこうです。
procedure bubbleSort( A : list of sortable items ) defined as:
  for each i in 1 to length(A) - 1 do:
       for each j in 2 to length(A) - i + 1 do:
         if A[ j ] < A[ j - 1 ] then
           swap( A[ j ],  A[ j - 1 ] )
         end if
       end for
  end for
end procedure
TeX でこれを実装したいのですが、いつものように「配列がない」という大きな問題でつまずきます。配列っぽいものを TeX で実現する手法はいくつか考えられますが、ここでは「TeX で brainfuck」のときと同じく count レジスタを使うことにします。バブルソートのアルゴリズムは配列をなめるループが2重になっているので、配列のインデックス用のレジスタも 2つ必要です。また、配列の全長(length)を格納しておくレジスタもあるとうれしい(これも 2つ必要)。そのほか、要素の入れ替え(swap)をする際に一時的に利用するレジスタもほしい(これも 2つ)。
%% count1からcount99を明示的に使うので\newcountで割り当てるわけにいかない。。
\countdef\p=0 \countdef\q=100
\countdef\length=200 \countdef\l=201  %% 配列の全長用
\countdef\i=202 \countdef\j=203       %% スワップ用
これだけ準備したら、冒頭のアルゴリズムを素直に書き下します。カウンタの値を増やしたり減らしたりするのに細心の注意が必要ですが、まあそのままです。
\length=0
\p=1

\def\sort#1{%
  \initArray#1\last
  \p=0 \q=1 \k=\length
  {\loop \advance\p by 1 \advance\k by -\p \advance\k by 1
    {\loop \advance\q by 1
           \i=\count\q \advance\q by -1 %% i = A[p]
           \j=\count\q \advance\q by 1  %% j = A[p-1]
           %% swap A[p] and A[p-1] when A[p] > A[p-1]
             \ifnum\i<\j \global\count\q=\j \advance\q by -1
                         \global\count\q=\i \advance\q by 1 \fi
           \ifnum\q<\k \repeat}
    \ifnum\p<\length \repeat}}
ここで、配列の初期化をする \initArray はこんな定義になっています。
\def\initArray#1{%
  \ifx#1\last
    \let\next=\relax
  \else
    \global\count\p=#1
    \global\advance\length by 1
    \advance\p by 1
    \let\next=\initArray
  \fi\next}
\initArray\loop...\repeat を使っていない理由は特にありません。
結果を出力するマクロも必要ですね。
\def\showArray{%
  \p=1 \advance\length by 1
  {\loop
     \number\count\p\ \advance\p by 1
   \ifnum\p<\length
   \repeat}}
実装おしまい。上記のコードと以下の実行例を pdftex のプロンプトにコピペすれば(最初の \relax を忘れずに)……
\sort{{314} 4 8 1 2 3 {42}}
\showArray
\vfill\eject\end
こんな結果のPDFが得られるはず。
1 2 3 4 8 42 314
なお、ソートできるのは 1以上の自然数だけです。


まとめっぽいもの

TeXのプログラミングが難しい背景には、ろくなデータ構造がないという側面も大きい気がします。配列でもリストでもいいのですが、とにかくトークンの列だけでは面倒なことは多すぎる。

大事な話として、 count レジスタを配列に使ってしまうと別の目的に使えなくなり、これは実用上かなり無茶な制約なので、この記事はあくまでもネタでありトイプログラムです。ちなみに TeX で配列っぽいものを実現する別の方法としては、1以上の自然数 n を配列のインデックスに見立て、\n で値を表現する方法(\def\1{42} \def\2{314} ... といった具合)などが知られているようです。詳しく知りませんが検索したらそんな論文が出てきました(→ "Sorting within TeX"(PDF))。

ネタ元

マクロツイーター:TeX での末尾再帰 (5)の「問題 5」。バブルソートなら簡単ちゃうかなあと思ったら、案外と「実行制御」につまずきました。白状すると、\sort の実装はそんなに簡単ではなかったです。

2011/08/31

Parsec3 におけるパーサーの型

きっかけは try

いつものように Parsec2 で安穏とパーサーを書き始めたのですが、 try が増えそうな気配だったこともあり、これを期に「Applicative! Parsec3!」と思い立ちました。とっかかりとして、『Real World Haskell』 16章の例を Parsec3 で試してみることにします。
import Control.Applicative hiding ((<|>))
import Text.Parsec

parser = (++) <$> string "HT" <*> (string "TP" <|> string "ML")
Parsec2 だと、 RWH に堂々と「Parsec プログラマは型宣言を省略するのが通常になってます」と書いてあったりして、パーサーの型シグネチャをはしょって生きてきました。その感覚でこのコードをコンパイルしたところ、こんなクレームが。
No instance for (Stream s m Char)
arising from a use of `string'
はて、(Stream s m Char)ってなんだろう。調べてみると、 NoMonomorphismRestriction なる言語拡張を導入すれば解決するらしい(参照)。いわれるままソースの冒頭に {-# LANGUAGE NoMonomorphismRestriction #-} という行を追加すると、確かにコンパイルに通ります。なにこのおまじない。。

単相性制限

憤慨していたら @bonotake さんからヒントをもらいました(ありがとうございます)。
型定義が与えられていない関数の型を推論する際に、多相的な型が想定できる場合でもデフォルトの型(Integerなど)に決めうちする、という規則らしいです。そのオプションを指定するか、型定義を忘れず自分で書けw ということらしい
http://twitter.com/#!/bonotake/status/108445024378818562
つまり型推論のときの付加的なルールとして単相性制限というのがあり、それを無効にするためのオプションがこのおまじないみたい。それにしても、単相性制限と (Stream s m Char) のインスタンスがないというエラーの関係はさっぱりわかりません。

単相性制限のことはいったん忘れて、パーサーの型を明示することに挑戦します。Parsec2 であれば GenParser Char st [String] なのですが、Parsec3 のパーサーの型は RWH には説明がありません。 NoMonomorphismRestriction を指定すればコンパイルに通ることはわかっているので、ずるをして GHC に型を教えてもらいます。
*Main GOA> :t parser
parser :: (Stream s m Char) => ParsecT s u m [Char]
おや、こんなところに Stream が。確かに、この型クラスのインスタンスが決まらないとパーサーの型が決まらなそうだけど、どうして単相性制限を使わないことにすると型推論が通ってしまうのだろう?

単相性制限のことはもう一度忘れて、GHC が教えてくれた型を明示してコンパイルしなおしてみます。
parser :: (Stream s m Char) => ParsecT s u m [Char]
parser = (++) <$> string "HT" <*> (string "TP" <|> string "ML")
結果
Non type-variable argument in the constraint: Stream s m Char
(Use -XFlexibleContexts to permit this)
In the type signature for `parser':
parser :: (Stream s m Char) => ParsecT s () m [Char]
制約に型変数じゃないものがあるとな。しかも、回避したければやっぱりおまじないを唱えろと。

Parsec3 におけるパーサーの型

おまじないはいやなので、 ParsecT が何ものかを調べてきっちりナシを通すことにします。まずは Hackage のドキュメントを眺めます。すると次のことがわかりました。
  • パーサーの型は ParsecT で、これはモナド変換子である。
  • 厳密な型シグネチャは ParsecT s u m a
    • s は抽象化された入力の型。この抽象化された入力をストリームという。
    • u はパース時に好きな状態を格納しておく容器の型。
    • m はモナド変換子にとって基盤となるモナド。
    • a は出力の型
さらに、Stream というストリームを用立てるための型クラスがあって、そのインスタンスを作ることでパーサーへの入力の型が決まるようです。Stream のインスタンスになるには、入力の型(String とか)と、それを読み込んでいくときの単位の型(Char とか)、入力から 1単位だけ読み出して残りと組にして返す関数が必要です(実際には得られる組を Maybe でくるんだものをさらに基盤となるモナドでくるむので、そのモナドも与えます)。

ようやく見えてきました。parserの型、つまり文字列から文字列を探して返すパーサーの型は、 Monad m => ParsecT String u m String です。
import Control.Applicative hiding ((<|>))
import Text.Parsec

parser :: Monad m => ParsecT String u m String
parser = (++) <$> string "HT" <*> (string "TP" <|> string "ML")
結果
*Main GOA> parseTest parser "HTTP"
"HTTP"
もちろん必要なら um に具体的な何かを指定できます。何も例が思いつきませんが、いろいろ検索してみたら、ユーザーステートを配列代わりにして brainfuck を作り、処理結果を IO に直接吐き出すという使い方が……。

Haskell: ParsecのParsecによるBrainfuck(SnowClust)
http://inviernostring.blog106.fc2.com/blog-entry-32.html

ところで Parsec3 のパーサーの型は、brainfuck を作ったりするのでなければ、Parser String という具合にお手軽に指定できるようです( Text.Parsec.String が必要)。
import Control.Applicative hiding ((<|>))
import Text.Parsec
import Text.Parsec.String

parser :: Parser String
parser = (++) <$> string "HT" <*> (string "TP" <|> string "ML")
これは、こういう定義が Parsec に用意されているからです。
type Parsec s u = ParsecT s u Identity
type Parser = Parsec String ()
つまり Parser という型変数を使うということは、入力としては String が、ユーザーステートとしては () が、基盤となるモナドとしては Identity が使われているということですね。

結局、単相性制限と最初のエラーの関係は?


もやもや中。

2011/08/25

技術書の索引にかける3つの覚悟

技術書の索引を作るのは、一筋縄ではいかない仕事です。
「IT書籍の索引について考える」(KeN's GNU/Linux Diary)
矢吹さんのtweetから始まって、まとまりはないけど索引について思考実験。オチはないよ。
まず索引というのは、そもそも正しいページを参照するものでなければなりません。42ページを見よとなっているのに、そのキーワードも関連する概念の説明も42ページに出現しないのはまずい。また、キーワードAに対して「キーワードBを見よ」などとなっている場合もありますが、これが循環しているのもまずいです。Aの項にもBの項にも具体的なページ番号がないのは論外ですが、「…見よ」が循環しているのもまずいです。こうした参照先について要求されることがらを「索引の健全性」と呼びます。(いま作った用語。このほか、キーワードが正しく五十音順に並んでいることなんかも健全性に含まれますね。)

一方、索引には、キーワードがあますところなく網羅されている必要もあります。こちらは、いわば「索引の完全性」です。索引の完全性は、「このキーワードの説明ページに飛びたい」に応えることだけではありません。「本に出現するキーワードはわからないけど、こんな話ってどこに書いてあるんだろう」とか「この話、あのキーワードの近くに説明があったような……」といった漠然とした要求にも応えられるよう、キーワードが選ばれている必要があります。

読者にとって索引の悲劇というやつは、たいてい、完全性が満たされていない事態です。健全性がまるでないことが発覚した本は、発行後に回収される場合さえあるので、作ってるほうはあまり手を抜いてません。健全性は機械的に(必ずしも自動的にという意味ではない)チェックができる性質でもあります。時間や人手が足りないなら、項目を減らせばいい。かくして完全性からは程遠い索引(けど健全)ができあがります。@kmutoさんが指摘するように、索引はページ割りが確定しないと着手できない場合も多く、それはスケジュール上は時間や人手が最高に足りない時間です。つまり索引の完全性は、なりゆきで本を作っていると、おのずから低下します。少しでも完全性の高い索引を作るには、著者や編集者にそれなりの心構えが必要ということです。心構えというより、むしろ覚悟がいります。
  1. キーワードの選択と参照先ページの決定(この両方の作業をインデキシングといいます)には時間がかかるという覚悟
  2. インデキシングに著者を巻き込む覚悟
  3. 著者にはできないインデキシングもあるという覚悟
キーワードを選び出す仕事は索引作りの端緒にすぎません。そのキーワードを参照すべきベストなページ番号を選び出す仕事、あるいは、そのキーワードの文脈を判断して必要があれば複数の索引項目や階層的な索引項目にする仕事が不可欠です。もちろん、頭から眺めてそれっぽいキーワードをピックアップし、PDF で検索して出現ページを調べ、それを組にして五十音順でソートすれば、健全性の保たれた索引はできあがります。でも完全性を目指した索引は、全文検索とは違う、ひとつのコンテンツです。だから、索引作りには時間がかかります。少なくとも書籍全体をメタに読み直すだけの時間がかかります。(というわけで電子書籍にもいかした索引は必要なんですよ。)

で、その時間を誰に割いてもらうか、というのが上記の2.と3.です。@kmuto さんがいうように、本の中身をいちばんよく知っているのは著者であり、著者が主体的にインデキシングした本はよい索引になることが多いです。が、ページ割が決まった後でないとインデキシングできないのでは、著者に作業をお願いしにくい。この点、LaTeXとかXMLからの自動組版は秀でています。なにせ、著者が自分の手で原稿に入れたメタ情報をそのまま情報を落とさずに利用できるのだから。つまり、こういう要求にも応えられる。
自分のDebian徹底本では索引用に5種類くらいのタグを使い分けて後処理していた。当時は紙に組んだら情報がかなり落ちてしまったのでいろいろもったいなかったんだけど、今ならもうちょっとうまい方法を思いつけそう
http://twitter.com/#!/kmuto/status/106236697867599872
実際、『RailsによるアジャイルWebアプリケーション開発』という本の第2版以降は、原著のXMLデータ(著者本人が作ってるもの)からあらゆるインデキシングに関する情報を抜き出しているつもり。(第4版はもうちょっとお待ちください。)

とはいえ、著者が作ればうまい索引になるというわけでもないのだよなあ。そこで、だからこそ編集者がいるんだ、というポジショントークです。最近の自分がかかわった例だと、『Coders at Work』という読み物があるのですが、これ、ちゃんと索引つけています。なんで読み物に索引ついてるんだという話ですが、この記事で最初のほうに言及した「この話、あのキーワードの近くに説明があったような……」に応えるためというのと、複数の話者のインタビュー集なので、同一の話題をパラレルに参照可能な手段があるほうがよかろうなと考えたからです。たとえば「テスト」のような項目を引いてもらうとわかりますが、別々の話者が主にテストについて語っている部分に飛べるようになっています。「テスト」というキーワードの直接出現がない場合もあるので健全性を一部犠牲にしていますが、後悔はしていない。なお、本書は原書にも索引はあるのですが、これが典型的な「それっぽいキーワードの出現箇所を適当に参照してみました」な出来だっていたので、結果的には参考にしませんでした(どうやら英語圏の大手出版社には「indexer」という専門職がいるようなので、その仕事っぷりによって索引の出来が大きく左右されてるような気もします)。

とはいえ、編集者が不要なインデキシングができるすごい著者はたくさんいて、たとえば『プログラミングのための確率統計』の著者の方々とか。それでも、編集者としては、たとえば「Γ関数」を「G」からも「記号・ギリシャ語」からも「カ行」からもひけるようにしました。いや、もしかしたらこれも平岡さんがやったのだったっけ? そこはかとなく気もしてきましたが、とにかくこれはすごい本です。

2011/08/16

TeXでナベアツ

以下の動作を行うマクロ \NabeAzz を作れ。
http://d.hatena.ne.jp/zrbabbler/20110815/1313398638


一晩すぎたっぽいし、回答例アップしてもいいよね。
\newcount\n \newcount\i
\newcount\r \newcount\q
\newcount\a \newcount\b
\newcount\d
\font\cmfi=cmfi10 at 12pt

\newif\ifhasdigit
\def\hasdigit#1#2{\d=#1 \r=10 \q=#2 \a=\q
\divide\q by \r
\b=\q \multiply\b by -\r
\advance\a by\b
\ifnum\a=\d \hasdigittrue
\else \ifnum\q=0 \hasdigitfalse
\else \hasdigit{\d}{\q}\fi\fi}

\newif\ifdividep
\def\dividep#1#2{\r=#1 \q=#2 \a=\q
\divide\q by \r
\b=\q \multiply\b by -\r
\advance\a by\b
\ifnum0=\a \divideptrue
\else \dividepfalse \fi}

\def\NabeAzz#1{\n=#1 \i=1
\loop \ifnum\i<\n
\hasdigit{3}{\i}\dividep{3}{\i}
\ifhasdigit{\cmfi \number\i}\else
\ifdividep{\cmfi\number\i}\else
{\number\i}\fi\fi
\advance \i by 1
\repeat}

\noindent\NabeAzz{400}

\vfill\eject\end
とくに奇妙なことはしていません。10進表記でない整数(16進とか8進とか文字とか)も引数にとれるところがちょっとポイント。アプローチは FizzBuzz とまったく同じ。FizzBuzz もナベアツも、整数の剰余をどう扱うかに TeX ならではの難しさがあるので、TeX マクロを使えるかどうかよりも、初等代数的なことを考える力を試されている気分。

参考文献はいうまでもなく "TeX Book" 一択。日本語版だと 296ページに出てくる \hex マクロを参考にしています。

はじめてでも安心 SXML入門

HTMLやXMLの文章を扱っていると、気楽にこんな操作をしたいケースが間々あります。

  1. 親に応じて処理を分けたい(例: <title> 要素に対して、その親が <chapter> なら処理 A 、 <section> なら処理 B をしたい)
  2. ある属性を持つ要素だけ処理したい(例: lang="en"<p> 要素だけ抜き出したい)
  3. 同じレベルの次の要素に応じて処理をしたい(例:表の各行の色を縞々にするため、 <table> 直下の <tr> に交互に属性 bgcolor="#cccccc"bgcolor="#ffffff" をつけたい)
(いずれもブラウザへの表示であれば CSS で実現できますが、いまは出力も XML データとして欲しい場合の話です。)

XML データの本質が木構造であることを思い出すと、この山括弧が S 式にさえなっていれば、 Scheme の関数でどうにでも操作できる気がしてきます。SXML は、まさにそのような夢をかなえてくれるものです。詳しくは Wikipedia で。ここでは、Gauche に入っている XML から SXML へのパーザーと SXML の操作関数、山括弧表記へのシリアライザを使って、SXML データを操る方法をまとめます。

XML データを SXML へ

何も考えずに ssax:xml->sxml というユーティリティを使います。自分の目的に応じた XML パーザを作る仕組みも提供されているのですが、私は使ったことがありません。

(use sxml.ssax)

(call-with-input-file "test.xml"
  (lambda (port)
    (with-module sxml.ssax
      (ssax:xml->sxml port '()))))

ただ一点だけ、このユーティリティを黙って使う際にぶつかる壁があって、それは文字実体参照です。定義済み実体しか解釈してくれません。そのため、たとえば &nbsp; が混ざっているとはじかれます。 &nbsp; は HTML でよく使われるので、これはいささか不便です。

ssax:xml->sxml には外部の文字実体参照を指定できる仕組みがないので戸惑うところですが、 sxml.ssax のソースを見ると ssax:predefined-parsed-entities というのがあって、これをつつけばよさそう。幸い、 Gauche には with-module という仕組みがあるので、これを使って文字実体参照のデフォルトを「上書き」してしまいます。

(use sxml.ssax)

(call-with-input-file "test.xml"
  (lambda (port)
    (with-module sxml.ssax
      (fluid-let ((ssax:predefined-parsed-entities
                  (list '(nbsp . " ") '(amp . "&") '(lt . "<") '(gt . ">")
                        '(quot . "\") '(apos . "'")))
        (display (ssax:xml->sxml port '()))))))

これで次のようなファイル "test.xml" が、

<body>
  <p>ab&nbsp;c</p>
</body>
次のような S 式になります。
(*TOP* (body (p "ab c")))

肩慣らし:要素の取得

とりあえず、これから使うサンプルの XML データを用意します。

(define newbooks "
<table>
  <thead>話題の新刊</thead>
  <tbody>
    <tr><th>書名</th><th>価格</th></tr>
    <tr><td>抽象によるソフトウェア設計</td><td>4500円</td></tr>
    <tr><td>インターネットのカタチ</td><td>1900円</td></tr>
    <tr><td>Scheme修行</td><td>2800円</td></tr>
    <tr><td>Coders at Work</td><td>2800円</td></tr>
  </tbody>
</table>
")

また、 XML の山括弧で表現されたテキストを SXML のデータに変換する関数をでちあげます(先に使ったコードと本質的に同じもの)。

(define (string->sxml str)
  (call-with-input-string str
    (lambda (port)
      (ssax:xml->sxml port '()))))

これから何度か利用するので、テーブル newbooks を SXML に変換したものに名前をつけておきます。これは恣意的に root という名前にします。

(define root (string->sxml newbooks))

準備はここまで。肩慣らしにテーブルからヘッダ部分を取り出してみましょう。実は XPath のような指定が使えます。

gosh> ((sxpath "table/thead") root)
=> ((thead "話題の新刊"))

"table/thead"」の部分には、求めるノードへのパスを XPath に似た(ほとんどそのまんまの)構文で記述できます。その記述に関数 sxpath を適用すると、ノードの集合からノード(記述したパスを満たすもの)を取り出す関数(コンバーターと呼びます)が返ってくるので、それを root に適用すれば求めるノードが手に入るという仕掛けです。

よりScheme風に

関数 sxpath は便利で多機能なのですが、クエリを組み立てるのに XPath の文法を知らないといけないので、ここではもうちょっと Scheme ふうのやり方を紹介します。

gosh> ((node-closure (ntype-names?? '(thead))) root)
=> ((thead "話題の新刊"))

この方法も、「ノードの集合からノードを取り出すコンバーター関数を作って、それを root に適用する」という方針は sxpath バージョンと同じです。コンバーターの作り方がちょっと違います。 node-closure という関数は、引数として述語をとり、その述語を満たすようなノードを「再帰的に」とってくるコンバーターを作ります。「再帰的に」というのは、述語を満たすノードをSXML木の根っこを起点に探すだけでなく、あらゆる子孫(大本の根っこも含みます)を起点に探すという意味です。この例では、「thead という名前かどうか?」( (ntype-names?? '(thead)) )という述語を指定しているので、とにかく木全体からその名前のノードをとってきます。

ところで、得られるノードは1つとは限りません。

gosh> ((node-closure (ntype-names?? '(td))) root)
=>((td "抽象によるソフトウェア設計") (td "4500円") (td "インターネットのカタチ") (td "1900円")
   (td "Scheme修行") (td "2800円") (td "Coders at Work") (td "2800円"))

ここで気にかけておくべきなのは、こうして REPL から結果がリストとして返ってくるとまるで結果のノードからなる新しいリストが得られたように錯覚するけれど、これらはちゃんと root の部分木になっているということです。つまり、これらの結果から、例えば「親」を知ることができます。

gosh> (define tds ((node-closure (ntype-names?? '(td))) root))
gosh> (((sxml:parent (ntype?? '*any*)) root) (car tds))
=> ((tr (td "抽象によるソフトウェア設計") (td "4500円")))

(ntype?? '*any*) は「何でもいい」という述語です。この式は、親( sxml:parent )なら何でもいいからとってこい、という意味になります。

くどくいようですが、上記の (car tds) は表現としては (td "抽象によるソフトウェア設計") というリストだけれども、そういう表現のリストに対して上記の結果が得られるのではありません。(当たり前の話だけど、最初はやっぱり見た目に惑わされがち。)

gosh> (((sxml:parent (ntype?? '*any*)) root) '(td "抽象によるソフトウェア設計"))
=> ()

実践編:ノードを追加する

SXML は木にすぎないので、その一部を変更するような操作も、 Scheme のような言語であればわりと直感的に書けます。ここでは、先の書籍一覧のテーブルの各行に「税」という項目を追加してみます。もちろん税額は、すでにテーブルに含まれている価格から求めます。そこでまず、価格の文字列から税額の文字列を作る関数を定義します。ここでは安直に正規表現で。

;; string -> string
(define (tax-price str)
  (rxmatch-if (#/([\d]*)円/ str)
      (m price)
    #`",(* 5/100 (x->integer price))円"
    #f))

次に、この tax-price 関数を使って「 <tr> ノードから税額を要素に持つ <td> ノードを作る関数」を定義します。なお、 SXML の一部をいじる関数を作るときは、このようにノードからノード(あるいはノードの集合)への関数として定義しておきましょう。そのほうが SXPath 用の適用関数をいろいろ利用できて便利です。

;; node -> node
(define (tax-node node)
  (cond ((tax-price (sxml:string-value node)) => (pa$ list 'td))
        (((sxpath "th") tr) '(th "税"))
        (else '())))

あとは各行に tax-node で得られるノードを要素として追加するだけですが、要素を追加する関数は Gauche にはないようなので、既存の要素 (sxml:content-raw tr) と新しいノード (tax-node tr) とを連結したもので既存のノードを置き換えます。木の一部を破壊的に変更する必要があるので、きちんと避難しましょう(『 Scheme 修行』ね)。

(let1 root (string->sxml newbooks)
  (for-each (lambda (tr)
              (sxml:change-content! tr `(,@(sxml:content-raw tr) ,(tax-node tr))))
            ((node-closure (ntype-names?? '(tr))) root))
  root)

=> (*TOP* (table (thead "話題の新刊") (tbody
      (tr (th "書名") (th "価格") (th "税"))
      (tr (td "抽象によるソフトウェア設計") (td "4500円") (td "225円"))
      (tr (td "インターネットのカタチ") (td "1900円") (td "95円"))
      (tr (td "Scheme修行") (td "2800円") (td "140円"))
      (tr (td "Coders at Work") (td "2800円") (td "140円")))))

軸とか

テーブルのいちばん左の列をヘッダのように使いたいことがあります。各 <tr> ノードの最初の要素に bgcolor="#cccccc" とつけるにはどうしたらいいでしょうか。

ぱっと思いつくのは、先行する要素がなければ属性を追加する、という処理です。 SXML では、 XPath のように、親や子孫、先行や後続といった「軸」が利用できます。これを使って、各行の先頭の要素に背景色の属性を付けてみましょう。

(let1 root (string->sxml newbooks)
  (for-each (lambda (td)
              (if (null? (((sxml:preceding-sibling sxml:element?) root) td))
                  (sxml:set-attr! td '(bgcolor "#cccccc"))))
            ((node-closure (ntype-names?? '(td th))) root))
  root)

使っている軸は、先行する兄弟の軸 sxml:preceding-sibling です。 <td> 要素のそれぞれについて、先行する兄弟の要素がいるかどうかチェックし、いなかったら属性を設定しています。要素の軸は、その要素が含まれる木(もちろん部分木のことも)に対して決まるものなので、 ((sxml:preceding-sibling sxml:element?) root) のように木を明示する必要があります。

=> (*TOP* (table (thead "話題の新刊") (tbody
      (tr (th (|@| #0=(bgcolor "#cccccc")) "書名") (th "価格"))
      (tr (td (|@| #0#) "抽象によるソフトウェア設計") (td "4500円"))
      (tr (td (|@| #0#) "インターネットのカタチ") (td "1900円"))
      (tr (td (|@| #0#) "Scheme修行") (td "2800円"))
      (tr (td (|@| #0#) "Coders at Work") (td "2800円")))))

なお、ここではわざわざ軸を使っていちばん左の列を選択しましたが、 1つめの要素、のような指定の仕方で取得することももちろんできます。

(let1 root (string->sxml newbooks)
  (for-each (lambda (tr)
              (sxml:set-attr!
               (car ((node-pos 1) ((node-closure (ntype-names?? '(td th))) tr)))
               '(bgcolor "#cccccc")))
            ((node-closure (ntype-names?? '(tr))) root))
  root)

山括弧にするには

良し悪しは別にして、やはり山括弧に戻せないといろいろ不便です。とくに html をいじっている場合には。Gauche のマニュアルを見ていると sxml->html という名前が付いた関数が二種類(sxml:sxml->htmlsrl:sxml->html)ありますが、ここで使うべきは srl:sxml->html のほうです。

(use sxml.serializer)

(srl:sxml->html
 (let1 root (string->sxml newbooks)
   (for-each (lambda (tr)
               (sxml:set-attr!
                (car ((node-pos 1)
                      ((node-closure (ntype-names?? '(td th))) tr)))
                '(bgcolor "#cccccc")))
             ((node-closure (ntype-names?? '(tr))) root))
   root))

よろしくインデントされた結果が返されます。

<table>
  <thead>話題の新刊</thead>
  <tbody>
    <tr>
      <th bgcolor="#cccccc">書名</th>
      <th>価格</th>
    </tr>
    (略)
  </tbody>

</table>

最後に宣伝

『Scheme修行』

ちなみに、この記事のタイトルは『はじめてでも安心コスプレ入門』から

2011/08/13

3才、補助輪なし自転車への挑戦

3才半の息子のために自転車を買いました。補助輪もついていたのですが、取り付けずにさっそく公園にいきます。



気持ち下り勾配のある場所なのですが、はじめてペダルつきの自転車に挑戦して、いきなり一人で乗ることができました。我が子ながらよくやったと思います。ちなみに、この自転車の商品名は「いきなり自転車」です。先日『インターネットのカタチ』という本にからめたイベントの懇親会でこの自転車の販売会社の方と知り合い、旧モデルをゆずっていただきました。ありがとうございます。

で、まあ親バカ記事ではあるのですが、なんで3才半でいきなり自転車に乗れるのかを知りたい人はけっこういると思うので、種明かしをします。実は、彼は1才半ころからこれで遊んでました。

balance bike

ペダルなしの二輪車です。一般名称としてはバランスバイクとか呼ばれています。この「ストライダー」という商品を選んだのは、Webでいろいろ調べたところ、さまざまな配慮がなされた商品であるように見受けられたからです。当時は日本の代理店が皆無だったので個人輸入しました(自転車は車両扱いで関税がかからない!)。最近は日本でもふつうに手に入るようになり、街でも見かけるようになりました。

車体がそこそこ軽いので、1才も終わりになると自分で取り回せるようになりました。足で地面を蹴って進むので1才でもよちよちながら走行を楽しめます。しかし、まがりなりにも二輪車なので、ある程度のスピードで走るには前進速度とバランスに対する感覚が必要になります。また、公道を走るからには、交差点で止まるとか、他人がいるときは飛ばさないとか、そういう交通に対する理解も培っていく必要があります。

最近ではこれくらいには乗りこなせるようになっていました。



15秒付近から両足を離して滑走しています。(個人差はあるでしょうが、たいていの子はしばらく練習すればこれくらい走れるようです。)

というわけで、3才半でいきなり補助輪なし自転車で走れたのは、このバランスバイクで養った何かのおかげではないかと推測しています。もちろんサンプル数が高々1なので推測の域は出ませんが、自分はかつて苦労して補助輪はずしの練習をした覚えもあるし、バランスバイクの影響がまったくないということはないように思います。

ただ、補助輪なしで走れるには走れるのですが、3才半だと自転車の本体が重くて取り回しがうまくできないのと、ペダルをこぎ出すための筋力が足りないようで、自力でゼロからこぎ出せるわけではありません。最初の動画のように下り勾配がある場所ならいけるのですが、そうでなければ助走が必要です。「いきなり自転車」の後ろには大人が制御するための舵取り棒がついていたので、これはたいそう助かりました。

2011/08/10

TeXで「最後のコマンド」だけ挙動を変える(ワンパス版)

@munepixyzさんのツイートで、あるコマンドの挙動を最後の出現のときだけ別なものにしたい、というTeXフォーラムへの質問があったことを知りました。

最後のコマンド
http://oku.edu.mie-u.ac.jp/tex/mod/forum/discuss.php?d=691&parent=3733

すでに、LaTeXで.auxファイルを使うというお手本のような回答があがっていますが、くだんのコマンドが本文のトップレベル階層にしか出現しない場合には、texコマンドを一回起動するだけで求める動作を実現できます。TeXフォーラムに登録していないため、ここでネタにします。

いつもは文字列「hoge」を出力し、最後の出現だけは文字列「fuga」を出力するコマンド\hogeの定義はこれだけです。(最後の\fiと#2の位置が逆だったバグを直しました(2012/2/27)。utさんコメントでの指摘ありがとうございます。)
\long\def\hoge#1\hoge#2{%
  \ifx#2\end fuga#1#2%
    \else hoge#1\expandafter\hoge
  \fi#2}

こんなふうに使います。pdftexを起動してインタプリタに以下を貼り付ければ、最後の\hogeだけがfugaに置き換わったPDFができあがります。
\hoge \hoge $1+2=3$ \hoge

\hoge \hoge

oshimai
% 本文はここまで

\hoge\end % この行を本文の最後に追記する
\eject

\hogeの定義では、引数を指定する部分が#1\hoge#2というあまり見慣れない形をしています。これは大雑把に言うと「次に\hogeが出てくるまでを#1に、その\hogeの直後のトークンを#2に束縛して、定義の本体を実行せよ」という意味です。このように、TeXのマクロでは引数のパターンマッチができる!

実際には1つのパターンでしか定義できないので、これはパターンマッチでもなんでもないのですが、この「パターンでマクロの引数を取り出す」手法はTeXプログラミングの主要な武器のひとつです。少なくとも私はそうおもっているので、自分でもけっこう使う機会が多いです。これを知ってればlatex.ltxだってもりもり読めるよ。

2011/06/19

いかにして私は思い悩むのを止めてcall/ccを便利に使うようになったか

call/ccは神秘的です。(call/cc call/cc) とか、いまだにくらくらします。まあ、ゆっくり考えれば難しいことではないのですが、回答に至るプロセスを即答する自信はありません。しかも、そんな込み入った話なのに、(call/cc call/cc) なんていう式には実用性のかけらもない。ゼロです。採用面接の問題にはいいかも。Schemerを募集するような会社があるかどうか知りませんが。

そんなcall/ccの使い道としてよく引き合いに出されるのは大域脱出です。でも、たとえばケント・ディヴィグの教科書の例を見てピンとくる人はいるのでしょうか。引数にゼロがあったら掛け算をやめてジャンプする。ありがたみがまったく感じられない話です。かと思うと、その次の例でいきなり登場するのは「プリエンプティブ・マルチタスクを実装したエンジン」なるもの。わけがわかりません。

そこで、自分が「call/ccうれしい」と感じられそうな例を考えてみることにしました。大域脱出を強いられる状況を作るので、下準備が少し必要です。


いま、こんなプロシージャー「>i>」が欲しいとしましょう。

(>i> 1 10)
=> (1 2 3 4 5 6 7 8 9 10)

ようするに、整数を2つ与えると間を補完してくれるようなプロシージャーです。

>i> そのものを定義するのは簡単です。でも、一歩進んで、こんな具合にメタ定義できるようにすることを考えてみましょう。

(define-range-maker >i> int++)

ここで、

(define (int++ n)
  (+ n 1))

です。

define-range-maker のほうはマクロとして定義できます。

(define-macro (define-range-maker sym next)
  `(define (,sym from to)
     (let R ((i from) (result '()))
       (if (equal? i to)
           (reverse (cons i result))
           (R (,next i) (cons i result))))))

define-range-maker を使ったので、似たような補完プロシージャーをもりもり作れるようになりました。たとえば、 int++ の文字バージョン char++ を用意すれば、2つの文字の間を補完するプロシージャー >c> ができます。

(define (char++ c) 
  (integer->char (+ (char->integer c) 1)))

(define-range-maker >c> char++)

(>c> #\a #\k)
=> (#\a #\b #\c #\d #\e #\f #\g #\h #\i #\j #\k)
pizzaもどうぞ。
(define (kakko++ x)
  (cons x '()))

(define-range-maker >p> kakko++)

(>p> '(pizza) '((((pizza)))))
=> ((pizza) ((pizza)) (((pizza))) ((((pizza)))))

さて、よく考えたら >i>>c>>p> も同じ定義の繰り替えしです。やはりこんな具合にマクロ when を作ってまとめられないでしょうか?

(define-range-maker >++>
  (when integer? int++
        char?    char++
        list?    kakko++))

ぱっと思いつくのは次のような定義でしょう。 define-range-maker に渡すプロシージャー int++char++ がとることになる引数の種類に応じて、当のプロシージャーを返そうという戦法です。

(define-syntax when
  (syntax-rules ()
    ((_ pred proc)
     (lambda (x)
       (if (pred x)
           proc
           (error "unsupported arguments"))))
    ((_ pred1 proc1 pred2 proc2 ...)
     (lambda (x)
       (if (pred1 x)
           proc1
           (when pred2 proc2 ...))))))

しかし、これではうまくいきません。 define-range-maker には「自分への引数を調べてプロシージャーを返そうとするプロシージャー」が渡されることになるのですが、 define-range-maker はそんなものを求めていないからです。 define-range-maker が求めているのは、あくまでも、そのプロシージャーが返そうとしているプロシージャーのほうです。

こんなとき call/cc が使えます。「自分への引数を調べてプロシージャーを返そうとするプロシージャー」のことは忘れて、当のプロシージャーを返すようにしましょう。

(define-syntax when
  (syntax-rules ()
    ((_ pred proc)
     (call/cc
      (lambda (k)
        (lambda (x)
          (if (pred x)
              (k proc)
              (error "unsupported arguments"))))))
    ((_ pred1 proc1 pred2 proc2 ...)
     (call/cc
      (lambda (k)
        (lambda (x)
          (if (pred1 x)
              (k proc1)
              (k (when pred2 proc2 ...)))))))))
これでうまくいきます。
(define-range-maker >++>
  (when integer? int++
        char?    char++
        list?    kakko++))

(>++> '(coffee) '(((((coffee))))))
=> ((coffee) ((coffee)) (((coffee))) ((((coffee)))) (((((coffee))))))

DSLを作るようなときはマクロなどでメタな階層を積み重ねていくことが多々あると思うのですが、そんなとき、下層に潜らないと見えない条件に基づいて上層の機能をスイッチするのに call/cc はなかなか便利です。 call/cc なんて使わずに済ませることもできるのでしょうが、こんなふうに便利に使えることもあるので、ぜひみんな『『Scheme修行』』を読んで「なあんだ」と実感しておきましょう。

『Scheme修行』

ちなみに、この『Scheme修行』の制作にも使っているxml2tex.scm(名前とは裏腹に、変換のためのスクリプトというよりは変換を定義するためのスクリプト)でも、ここで紹介したのとまったく同じ方法でcall/ccを利用しています。というか、それを説明用の例として書き直したのがこの記事の when マクロでした。

2011/03/03

drop して take すればリストに窓が開く

激しく時代遅れな話題ですが……
[1,2,3,4,5] が与えられたとき [[1,2][2,3][3,4][4,5]] を返すような関数を定義せよ
http://valvallow.blogspot.com/2011/02/12345-12233445.html

これは、窓をガラガラっと動かしながらリストを覗く、という問題です。ようするに、こういう話(n = 3 の場合)。
            1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
リストに窓を作るには、drop して take するのが簡単です。窓をガラガラっと動かすほうは、この問題の場合にはリストを作ることになっているので、unfold 系の高階関数を使えばよいでしょう。Scheme よりすっきり書けるので Haskell で書きます。
import Data.List (unfoldr)

overlaps n ls = unfoldr (window n) ls

window :: Int -> [a] -> Maybe ([a], [a])
window 1 _ = Nothing
window _ [] = Nothing
window _ (x:[]) = Nothing
window n ls = Just (take n ls, drop (n-1) ls)
drop の引数が n-1 なのは、窓をガラガラっとする前と後で隙間が要素1つぶんだけ重なっているという問題だからです。

Scheme も SRFI-1 に unfold があるので、ほぼ同じように「窓をガラガラっ」(drop して take した window を unfold)という気分で書けます。Gaucheだと、リストの長さの制限がゆるいバージョンの take や drop が用意されているし(take*drop*。util.list を使う)、いつのころからか unfold も組み込まれているので(最近知ったunfold組み込みはうそでした。すみません)、なおよい。
(use util.list)
(define (overlaps ls n)
(unfold exhaust? (take$ n) (drop$ (sub n)) ls))

(define (sub n) (max 1 (- n 1))
(define (exhaust? ls) (or (null? ls) (null? (cdr ls))))
(define (take$ n) (cut take* <> n))
(define (drop$ n) (cut drop* <> n))
結果として、podhmoの日記 と同じ答になりました。

そういえば、どうして Scheme の take や drop は、リストでなく整数が最後の引数になっているんだろう…?

2011/01/26

TeX で Brainfuck

TeX というプログラミング言語があります。配列とループがないのが特徴です。配列とループがなくて困ることはあまりありませんが、その数少ない困る場面が、プログラミング言語 Brainfuck の実装でしょう。Wikipedia の記事を読む限り、バイト型の配列とそれを指し示すポインタ、それに while ループさえあれば、Brainfuck は簡単に実装できそうです。

しかし、ないものはないので、別の手段を考えます。ぱっと思いつく配列の実現手段は TeX のレジスタです。レジスタには、ある種のトークンやトークン列を一時的に保存できます。ちなみにトークンというのは TeX の入力の最小単位で、文字や整数のほか、グループ({...} でくくられたもの)やコントロールシーケンス(\foo のようないわゆる TeX コマンド)も 1つのトークンです。

レジスタにはいろいろな種類があるのですが、個々のレジスタには「種類 + 番号」のような名前が付いています。たとえば整数用のレジスタなら、\count0\count1\count2、... といった具合です。この整数値を格納できるタイプのレジスタを使えば、番号をインデックスに見立てることで、バイト型の配列的なものが実現できますね。ただ、このインデックスの値をプログラムで足したり引いたりするには、やはりレジスタに収めるしかありません。インデックスは整数なので、整数用のレジスタが余分に 1個必要です。\count0 レジスタを割り当てましょう。
%% init
\countdef\pointer=0
\def\init#1{%
\count0=0
\loop \advance\count0 by 1 \global\count\count0=0 \ifnum\count0<#1 \repeat
\global\pointer=1}
\init{255} とすることで、長さ 255 の配列がゼロに初期化されます。

あとは Brainfuck の仕様に従って処理を実装します。\run{...} で Brainfuck のプログラムが実行されるようにしましょう。
%% run bf program
\long\def\run#1{%
\init{255}
\apply#1!\relax}

%% applying each bf command
\def\apply#1{%
\let\next=\apply
\if#1>\increment\else
\if#1<\decrement\else
\if#1+\incrementp\else
\if#1-\decrementp\else
\if#1.\putbyte\else
\if#1,\getbyte\else
\if#1!\let\next=\relax\else
\dowhile{#1}\let\next=\apply
\fi\fi\fi\fi\fi\fi\fi
\next}
\apply は、トークンを 1つ取り込み、その種類に応じてインデックスを動かしたり整数レジスタの値を増減したりします。これをトークンがなくなるまで繰り返します。

繰り返し処理がややトリッキーに見えるかもしれませんが、ループのための便利な構文もないし、末尾最適化もしてくれないので、自分で末尾再帰するように書くしかないからです。まあ、なんのことはなくて、次の処理を \next\let して \next を最後に置けばいいのですが、たとえば再帰するつもりで各 \if 文の中に \apply\relax を直接置いてしまうと、メモリがうまって止まります。TeXのコマンドは実行時に評価されるわけではなく、定義に書いてあるトークンの列に置き換えられるだけなので、 \apply の素の定義がばらばらばらっと無限に繰り返し展開されてしまうからです。実は TeX の動作というのは、徹頭徹尾「トークンを置き換える」だけであり、この置き換えのルールやタイミングをうまく与えることが TeX プログラミングにほかなりません。

\dowhile 以外の各処理を実装しましょう。入出力がやや面倒ですが、ほかは素直に書けます。
\def\increment{%
\global\advance\pointer by 1}
\def\decrement{%
\global\advance\pointer by -1}
\def\incrementp{%
\global\advance\count\pointer by 1}
\def\decrementp{%
\global\advance\count\pointer by -1}
\def\putbyte{%
\ifnum\count\pointer<256
\char\count\pointer\fi}
\def\getbyte{%
\read 16 to \getchar
\ifx\end\getchar\else
\global\count\pointer=\getchar\fi}
残るは \dowhile ですが、これだけは正直てこずりました。前述のように TeX はトークンを次々に置き換えていくだけなので、簡単には前に戻れない。整数レジスタを使って対応する閉じ括弧 ] を探すことは簡単にできますが、そこまでのトークンをどこかに保持しておいて \apply の引数として渡すといった処理ができない。週末、子供と科学博物館にいったりレゴでアンモナイトを作ったりしてあげている間、ずっと考えてました。

アンモナイト LEGO

で、最終的に思いついたのが、 []\catcode を変更して TeX の字句解析をだます方法。 TeX は対応する {...} で囲まれた部分を 1つのトークンとして読み込むのですが、 Brainfuck の [...] を TeX のグループであるかのように読み込んでしまえば、対応する括弧で囲まれた部分を取り出すという問題そのものが消滅する!
\catcode`\[=1
\catcode`\]=2

%% [ and ] (while loop)
\newif\ifinnerloop

\def\dowhile#1{%
\ifnum\count\pointer=0 \relax\else
\loop {\apply#1!}%
\ifnum\count\pointer=0 \innerloopfalse\else\innerlooptrue\fi
\ifinnerloop
\repeat
\fi}
そんなわけで、たぶん Brainfuck っぽいものができました。とりあえず Wikipedia にあった Hello World! はうごく。

\run{
++++++++++[>+++++++>++++++++++>+++>+<<<<-]
>++.>+.+++++++..+++.>++.
<<+++++++++++++++.>.+++.------.--------.>+.>.
}

=> Hello'World!Ω
入れ子の括弧も大丈夫。以下は、今回おおいに参考にさせていただいた k.inaba さんのページから、掛け算の例。

\run{
++++>++><<
[-
>[->>+<<]
>>[-<+<+>>]
<<<
]>>
++++++++++++++++++++++++++++++++++++++++++++++++.
}

=> 8





FAQ

「むしろ TeX が brainfucking です。」

クヌース先生に言ってください。しかし、もしかするとあなたが brainfucking だと思っているのは、 TeX ではなく LaTeX のスタイルのほうではないでしょうか。あれは実際、brainfucking です。ドキュメントの見た目を制御するときにワケワカなのと、プログラミングがワケワカなのは、違う話です。スタイルとか気にしないで素の TeX プログラミングをするだけなら楽しいですよ。

「Hello World! に ' とか Ω とかゴミが出てるけど?」

TeX のデフォルトのテキストフォント cmr10 で、それぞれASCIIコードの 32 と 10 に割り当てられているグリフ(を再現したもの)です。

「入力のある Brainfuck プログラムから抜けられない」

\end と入力して、? とプロンプトが出たらリターンを押してください。運がよければエラーは出るものの dvi が生成されます。エラーは if 文が閉じてないとかそういうたぐいで、このエラーなしに再帰的なマクロを途中で抜ける方法を私は知りません。

The Brainfuck Archive のコードに動かないのがある」

そうですね。

実は 1つごまかしているところがあります。この実装は、 [...] の内側にコマンドが 1つしかない場合に対応していません。たとえば [-]- を区別できないのです。これは [...] を TeX のグループとして読み込んでいることによる制限です。とはいえ、[-] としたい場面で [<>-] などと書くことにすれば、この制限を回避して等価なコードが記述できます。動作しない Brainfuck プログラムも、そんなふうに改修すれば動くかもしれません。

あと、制限としてはもうひとつ、配列の最大長が 255 です。これは TeX のレジスタが 256 個しかないからです(LuaTeX のような最近の実装を使えば 65535 とかまでいける)。上記のサイトのサンプルはかなり大がかりなものも多いので、この制限にひっかかって動かないものがけっこうありそう。

さらに言い訳が続きますが、上記のサイトのコード例で計算をする系のものには、出力結果を ASCII にしてないものがあるようです。そういうのは DVI や PDF にしたときに文字化けします。