2007/12/29


年賀状かいた。
%!
<< /PageSize [285 420] >> setpagedevice
newpath
/Times-Bold findfont 340 scalefont setfont
7 200 moveto
(2) false charpath

/Times-Bold findfont 230 scalefont setfont
148 250 moveto
(0) false charpath

/Times-Bold findfont 200 scalefont setfont
10 55 moveto
(0) false charpath

/Times-Bold findfont 440 scalefont setfont
67 10 moveto
(8) false charpath
clip
/Georgia-Bold findfont 13 scalefont setfont
0 11 450 { %for

/r1 {rand 23 mod 17 div} def
/r2 {rand 13 mod 17 div} def
/r3 {rand 17 mod 17 div} def
r1 r2 r3 setrgbcolor
newpath
0 exch moveto
(200820082008200820082008200820082008200820082008200820082008200820082008200820082008) show
} for

DSC_0326

2007/12/27

ケリー・リンクの『マジック・フォー・ビギナーズ』(柴田元幸訳)。英語の日記によれば11月5日に買ったものらしい。ほとんど2ヵ月かけて読んだわけか。本を読むのが仕事だというのに、この遅読っぷり。いろんな人の日記やブログを読んでいると、すごい勢いで書評やら新刊紹介やらが更新されていて、本当にすごいと思う。僕は今年、何冊本を読んだんだろう(仕事として編集したものは除く)。どうして早く読めないんだろう。何につっかかってるんだろう。

たしかにケリー・リンクの小説には、つっかかるところは多い。不条理さとか荒唐無稽さとか、そういう表面的なところにつっかかるんじゃなくて、お話のせつなさにつっかかってしまう。ぶっちゃけ小説の外面がどんなに奇抜だろうと、もともとそういう要素を求めて彼女の本を読んでいるわけだから、その部分は快感なわけだ。快感すぎるわけだ。だからこそ異化された現実感が巧妙に襲ってきて、せつなくなる。もうね、最後に収録されている「しばしの沈黙」なんて、奥様と離れて生活せざるを得ない状況にある男性に読ませたら、間違いなくきゅんとなるよ。僕もスターライトたんに電話して、悪魔とチアリーダーのお話を語ってほしい! あと、今の自分にとっては、「石の動物」に出てくるヘンリーにも共感を禁じえない。彼に限らず、ケリー・リンクの話に出てくる男性は、みんな斜めった現実に抗わなすぎ。そしてそれは、うちらの日常も一緒なんだろうな。そばにいるのが明らかなゾンビとか悪魔じゃなくて、ゾンビとか悪魔っぽい人間ってだけのことで。

ところでケリー・リンクの翻訳作品には『スペシャリストの帽子』もあるけど、もし彼女の作品を初めて読んでみるってことなら、『マジック・フォー・ビギナーズ』のほうをすすめます。訳文の出来が違う。

2007/12/26


日暮里に「深セン」というあんかけチャーハン専門のこだわり店がある。「セン」の字は、土に川って書くやつね。香港から中国側に川渡ったところの深センと同じ漢字。そういえば昔、深センの駅で写真とってたら、軍人にすごい怒られたことがあったなあ。どうでもいいついでに、個人的に深センといえばチャイナドレス。香港では滅多に見かけないチャイナドレスの女の子だけど、深センに入るとやにわに街に登場する。あれは観光客向けのサービスだったのかもしれない。市場なんかにいたのは、生きたトリを詰め込んだ籠を担いで外人の僕らにまで売ろうとする農民服の女の子とかだったからなあ。2000年ころの話。

で、日暮里の深センの話だけど、このあたりで生活している独身20代男性は毎日通うべきだと思う。うまい。この店の「あんかけチャーハン」は、同じようなメニューを食べさせてくれる店をほかに知らないので説明しにくいんだけど、ようは卵をからめてざっくりと炒めたライスに中華風の一品料理をたっぷりとのせたもの。一品料理のメニューとしては、麻婆豆腐、羊肉炒め、豚角煮炒め、青菜炒め、日替わり(鶏肉と旬の野菜炒めのことが多い)なんかが定番で、夜になると調達した食材に応じてさらに凝ったものが追加される。魚飯とか。こんなところでアドバタイズしてるくらいだから、どれも絶品なわけですよ。しかも安い。笑っちゃうくらいに熱いスープと工夫されたデザートが全品についていて、日替わりなんて600円なんだから。もっと高くてもいいのに。

調理をしているのはマスター一人だし、基本は全部注文を受けてから作るので、タイミングが悪いとけっこう待たされる。でもまあ、調理している手際を見ているだけで相当楽しい気持ちになれる。ビールでも飲んでればいいしね。青島が350円。青島の黒が500円。もうちょっと儲けてもいいんじゃないかって心配になるような値段。

R0011616

この店の料理がどれくらいうまいかっていうと、僕が今日レアメニューのビーフンをうっかり頼んじゃって(チャーハンじゃないメニューをはじめて見た)、ビーフンってのはしいたけ嫌いにとっては第一級の危険食品なわけだけど、やっぱり入ってて、幸い乾燥物じゃなくて生だったので皿全体には被害がなく、全体としてはむしろいつもの深センクオリティで、だからがんばって先に個体だけをビールで流し込んでから残りをおいしくいただいた。それくらいにうまいのです。(しいたけを身体に取り入れるのなんて、たぶんもう確実に20年以上ぶりだ。)

2007/12/13

GhostScriptでヒラギノを使うまとめ

ghostscript でヒラギノが使いたい。

PostScriptで遊んでいて歯がゆいナンバーワンは、後置記法なんかじゃなく、日本語の出力だったりする。gsまわりのパッケージを一通りインストールした Debian であれば、/Ryumin-Light-EUC-H とか指定するだけで、とりあえずは日本語を出力することはできる。でも、そのときに使われるフォントは、/var/lib/defoma/gs.d/dirs/fonts/cidfmap に登録されている TrueType のもの。職場にはきちんとハコで購入したヒラギノがあるのに、何が悲しくて微妙に美しくない TrueType のフォントを使わなければならないのか。最近の ghostscrpt なら OpenType のフォントにも対応しているはずだ。ところが、この cidfmap を編集しても、OpenType のフォントを使えるようにはならないらしい。

そこで、Debian の ghostscript でヒラギノ(というかOpenTypeフォント一般)を使う方法のまとめ。ghostscript のバージョンについては複雑すぎるので省略。少なくとも 8.15 では以下の2つを実行すればいい。
  1. FontResourceDir/CIDFont に、フォントのCIDデータを用意する。
  2. FontResourceDir/Fontに、フォントと同じ名前のファイルを作って、フォント辞書を生成するコードを用意する。
FontResourceDirは、Debianなら/usr/share/gs-esp/*.**/lib/gs_res.psで定義されているディレクトリで、やはりDebianなら/usr/share/gs-esp/*.**/Resource/ になる。(*.**はghostscriptのバージョン。8.15とか)

1. については、フォントのファイル(*.otf)へのシンボリックリンクをFontResourceDir/CIDFont に張っておけばOK。

2. で用意するコードは、たとえばUTF-8の横書き用ヒラギノ角ゴシックW3のものであれば、
/HiraKakuPro-W3-UniJIS-UTF8-H
/UniJIS-UTF8-H /CMap findresource
[/HiraKakuPro-W3 /CIDFont findresource]
composefont
pop
これをHiraKakuPro-W3-UniJIS-UTF8-Hという名前のファイルに保存して FontResourceDir/Font に置く。このファイルは、使いたいフォントとエンコードごとに必要。

ちなみに composefont は、スタックからフォント名とCMapのデータとCIDフォントのデータをとって、フォント辞書を生成し、それをスタックの一番上に積むオペレータ。だから最後に pop がいる。

これできれいな日本語が出力できるようになった。
/HiraKakuPro-W3-UniJIS-UTF8-H findfont 30
scalefont setfont
newpath 30 700 moveto ( いつまでも責了しないのは本じゃない。 ) show
newpath 30 660 moveto ( そんなのは、ただのドキュメントだ! ) show
newpath 380 620 moveto ( by ctakao ) show


japanese-test

2007/11/20

追記(2007/12/10)
報告が遅くなりましたが、Jin-Hwan Choさんにすぐに修正対応していただきました。

[cvs] Diff of /dvipdfmx/src/pdfdev.c http://cvs.ktug.or.kr/viewcvs/dvipdfmx/src/pdfdev.c?r1=1.63&r2=1.64

困っている人は、CVSの先端をビルドするか、20071115 のスナップショットに上記のパッチを当てるだけでも大丈夫そう。

追記ここまで


dvipdfmx をアップデートしたら2日間はまってしまった。どうやらetchの dvipdfmx には注意が必要らしい。

自分が遭遇した不具合は2つ。
  1. dvipdfmx(20050831)の picture 環境で、multiput の出力がずれる
  2. 細身のCourierフォント(pcrr8rn)が dvipdfmx で細くならない
    http://oku.edu.mie-u.ac.jp/~okumura/texfaq/qa/44806.html
このうち 2. のほうは updmap の問題らしく、TeX Q&A にある土村さんのパッチで解決する。

1. については、次のような2種類の図形を描いてみると問題がはっきりわかる。
\begin{picture}(100,10)%
\multiput(0,0)(.1,0){1000}{\circle*{10}}
\end{picture}%

\begin{picture}(100,10)%
\put(0,-10){\circle*{10}}
\put(100,-10){\circle*{10}}
\end{picture}%


上下の picture は、それぞれ同じ幅になることが期待されるけど、dvipdfmx(20050831)では multiput したほうが長くなってしまう。(20070518 でも同じ。)

dvipdfmx-result

dvips で ps にして Acrobat 7.0 で pdf にすると、期待どおり同じ長さになる。(以前のバージョンのdvipdfmx(20040411)でも同じ長さになる。)

dvipd-acrobat-result

原因も修正方法も解明できなかったので、とりあえず報告のメールを送った。

2007/10/20

やっぱりSocket370のマザーなんて壊滅だった。唯一すぐ手に入ったまっとうな商品が、SuperMicroのP3TSSEで、実売は23000円。どう考えても2007年の買い物じゃないけど、しかたない。メーカー取り寄せとのことだったけど、「Your Best P4 Motherboard」と書かれた箱に入ってやってきたので、もうメーカーにもまともな在庫はないのかもしれない。

supermicro p3tsse

でもこれ815EだしDDRは使えないんだよな。PC133のメモリも何枚か持っていたはずなんだけど、こないだ古いPCを処分したときに一緒に引き取ってもらっちゃたみたい。さすがにもう使うことはないだろうと思ったと思われる。しかたないのでメモリも買う。512MBで20000円。げー。いまどきPC133のSDRAMって信じられないくらい高額なのね……。6000円くらいのへなちょこなバルク品も試してみたんだけど、認識されなかった。最近はメモリの相性なんてほとんど気にしなくなってたので、ここでふたたび時代に取り残されている感。

2007/10/16

どうやらマザーボードを買い換えないとだめらしい。買い換えるのはいいんだけど、いまどきDDR PC2100に対応したまともなSocket370のボードなんて手に入るのかなあ。


やっぱりTualatinだよね。


tualatin

2007/10/12

SXMLから、ある属性を持つ要素をSXPathを使って取り出したい。たとえば英語と日本語の文章からなるこんなデータから、lang属性が"ja"の要素を取り出したい。
(define e
'(*TOP*
(p (|@| (lang "ja"))
"こんにちはこんにちは"
(emph "日本語です"))
(p (|@| (lang "en"))
"hellohello"
(emph "I got english"))
(m (p (|@| (lang "ja"))
"あしたは休み")
(p (|@| (lang "en"))
"I can't work any more"))))
どうやらSXPathで遊ぶときは、「真偽を返す関数」をsxml:xxxみたいな名前の関数に渡してコンバータを作り、それを要素に適用するというのが常套っぽい。いまは属性をもとに評価したいので、「真偽を返す関数」としては「lang属性の値が"ja"かどうかテストするプロシージャ」になるんだろうな。そして木全体をめぐりたいので、sxml:descendantで作ったコンバータをルートノードに適用すればよさそうだ。
(use sxml.sxpath)
(use sxml.tools)

(define (f r n v)
((sxml:descendant
(lambda (e) (equal? "ja" (sxml:attr e 'lang))))
r))

(define q (sxpath `(,f)))
実行結果
gosh> (q e)
((p (|@| (lang "ja")) "こんにちはこんにちは" (emph "日本語です"))
(p (|@| (lang "ja")) "あしたは休み"))

2007/10/09

つっかかるようなシューベルトが好きといえばわかる人にはわかるとおり、ぼくはアファナシエフの弾くシューベルトが好きだ。で、10月1日に彼が来日してシューベルト弾くというので、トッパンホールにいってきた。さすがに大御所の演奏会だけあっていい値段だったし、聴衆もおじいさんおばあさんが多くてなんだかなーという感じ。彼らの大半はシューベルトだけが目当てなんだろうな。でも残念でした。この日、アファナシエフが弾きたかったのは、途中の休憩をはさんで演目の真ん中に演奏したシルベストロフだったらしい。それをはさんで演奏したシューベルトの即興曲は、明らかに軽く流してた。すごくうまいけど。

そもそもシューベルトの特にピアノ曲は、例えばかわいい女の子といっしょにいる最中に「この楽しい時間はどうしていつか終わってしまうんだろう」みたいに思い始めてしまったときのあの何ともいえない気分が永遠に引きのばされる感じがたまらないと思うんだけど、そういう雰囲気には欠ける演奏だった。楽しい(pleasure)が幸せ(happy)に結び付くとは限らないってダライ・ラマは言ってるけど、だからこそ「楽しさをなんとか引きのばして幸せを錯覚したい」っていう気分にきゅんとなるわけで、そうでないシューベルトは老後の楽しみにはいかもしれないけど(なにしろアファナシエフはすごくうまい)、ぼくが聴きたいのとは違う。

アファナシエフはピアノソナタ18番をレコーディングしてるけど、そこではこの錯覚した幸せ感を満喫できる。こないだの高橋アキの13番にも同じ印象を受けた。10月1日のアファナシエフは、むしろシルベストロフの曲で、この感じを演奏者として楽しんでいた気がする。そういえば高橋アキはアンコールにサティの「おまえがほしい」を弾いて、それを聴いていたときは「ぶちこわしじゃん」と思ったけど、あの堂々巡り感も同じような世界観なのかもしれない。

2007/10/06

自分用のメモ。なんかこんな感じのリストがあるとする。
(define life
'(i (value "2007")
"年"
(ii (value "10")
"月"
(iii (en "9")
"日"))
(delimiter "/")
(value "2009")
"年"
(ii (value "1")
"月"
(iii (en "31")
"日"))
(iii (value "10")
"日")))
一番上の階層のテキスト情報だけを抜きたい。
つまり、iiとiiiのタグの子孫を飛ばして読んでいきたい。結果として得たい文字列は、"2007年/2009年"。

SXPathを使う。
(use sxml.sxpath)

(define (text-self elem)
(sxml:string ((node-self (ntype?? '*any*)) elem)))

(text-self
(cons 'dummy
((sxml:child (sxml:invert (ntype-names?? '(ii iii)))) life)))

2007/09/30

9月20日に高橋アキのピアノドラマティック・シリーズ #5にいってきた。高橋アキについては、一時期よくサティを弾いていた人という認識しかなかったし、今回の演目にもシューベルトの13番とかあったので、もっと「耳にやさしい」コンサートかと思ってたよ。自分のなかでシューベルトブームだったのと、チェロのローハン・デ・サラムが競演してコダーイの無伴奏チェロとかドビュッシーのソナタとかやるということだったので、それなら奥さまがチェロを聴いてみたがっていたしちょうどいいかと思ってチケットを買った。

で、このローハンおじさん、高橋アキがモンポウとかシューベルト13番のような聴き心地のいいクラシカルな演目をやるといってるところに「オレにコダーイをひかせろ」といって殴り込んできただけのことはある。メロディックなさじ加減ゼロ。たとえばヨーヨー・マのひくコダーイは、このむちゃくちゃハードな技巧の曲を可能なかぎり美しく聴かせようみたいなサービス精神に溢れているんだけど、ローハンおじさんにそんな気配はない。最後の一音なんて、ほとんど放り投げてるもんな(ヨーヨーのCDでは、たっぷり響かせて終わる)。もう、この演奏だけでファンになりました。翌週の27日にも渋谷でローハンおじさんが聴けるというので、もちろんそちらにもいってきた。

27日のほうは、フルートのカリン・レヴァインと競演で、現代曲オンリー。50人くらいしか聴衆のいない小さな演奏会だったけど、これがまた強烈だった。ローハンおじさんはコダーイに加えて、クセナキス(本人はゼナキスと発音してた)と松村禎三(!)のソロ曲を演奏してくれた。クセナキスはもともとプログラムにあって、期待もしてたんだけど、ゆうに期待を裏切ってくれる。松村禎三は、追悼として当日プログラムに追加されていた。17絃箏のための祈祷歌をチェロ独奏むけにアレンジしたものらしい。最初からチェロ独奏の曲でしょうっていうくらいの完成度なんですが、それは松村禎三の曲の力ですか、それともローハン・デ・サラムのうまさですか。なんかいま東京でレコーディングしているらしいんだけど、コダーイはもちろん、このクセナキスのKottosと松村禎三をなんとか収録していただけないものでしょうか。

この日はフルートのカリン・レヴァインさんもよかったな。とくにカイヤ・サーリアホとジャチント・シェルシ。フルートってこんなにいろんな音が出る楽器だったんですね。バスフルートやアルトフルートの独奏曲を生で聴く機会があるとは思いませんでした。

高橋アキを忘れていたわけではなく、9月20日の演奏にはものすごく共感するんだけど、いかんせん後にやった9月27日の演奏会の印象のほうが強烈に残っているのですっかり話がずれた。とくに、つっかかるようなシューベルトは、そうでなければシューベルトをあえて聴く意味はないよねと個人的にはすごく同意したい。

2007/09/23

現在の書籍の製作スタイルでは、横長のモニターを縦にして使うほうが都合がいい。とにかくビルドしたPDFのページを画面いっぱいに大きくして確認したいのである。そして書籍のページというものは横長ではなく縦に長い。ところが職場で使っている24インチのモニターはスタンドがへぼくて縦にして使うことができない。それでスタンドをはずして机の上に平置きにして無理やり縦にして使ってみた。

R0011978

これがあんがい使える。廃熱も大丈夫なようだ。

本当はPDFを見開きで確認したいので、このサイズのモニターが二枚はほしいところ。もちろんちゃんとアームつきでな。

2007/08/20

先週、2004年ごろには Gauche に KAKAI のライブラリがあったらしいとか書いたけど、sourceforge のページから今でもダウンロードできると shiro さんに教えていただいた。ありがとうございます。(そして、ろくに確認せず適当なことを書いていてすみません。)

しかもインストールしたら使えた。
(use text.kakasi)
(kakasi-begin :JH :p)
(display (kakasi-convert "素子"))
(newline)
(kakasi-end)
これを kakasi-trial.scm とすると、
$ gosh -V
Gauche scheme interpreter, version 0.8.10 [utf-8,pthreads]
$ gosh kakasi-trial.scm
{もとこ|そし}

2007/08/17

[2010年6月3日 追記]改良版はこちら

emacs で、検索パターンをその後の編集中ずっとハイライトにしたい。インクリメンタルサーチの結果でもハイライトされるけど、あれだと編集をはじめるとハイライトが解除されてしまうので、使えない。
具体的には、作業中のバッファであるパターンを一時的にハイライトして、それを確認しながらを作業するための方法がほしい。こんな具合。

highlight

この例は、 ja タグで囲まれた部分をハイライトするようにしたところ。普段はハイライト不要なんだけど、たとえば「 ja にも en にも出てくる語を検索して周辺の文章を編集したいんだけど en 内にあるほうは無視してもいいや」といった作業に便利じゃない?

で、とりあえず作ってみた関数。
;;; let designated pattern be highlight
(defun highlight-regexp (re)
(interactive "sRegexp: \n")
(make-face 'my-highlight-face)
(set-face-foreground 'my-highlight-face "black")
(set-face-background 'my-highlight-face "yellow")
(defvar my-highlight-face 'my-highlight-face)
(setq font-lock-keywords (list (list re 0 my-highlight-face t)))
(font-lock-fontify-buffer))
M-x highlight-regexp すると正規表現の入力を促されるので、そこで適切なパターンを指定すると、上の例のようにハイライトされる。そういえば解除するときのことは考えてなかった。あと、上の例ではなんとなく改行をまたいだマッチに成功してるけど、emacs の正規表現である以上、改行をまたいだパターンのマッチは期待通りにいかないと覚悟すべき。やっぱりこのエディタは、本当のところは文章のパワー編集には向かないんじゃないだろうか。

まあ問題はあるけど、ここまではよかった。

実はいま c さんに、「xyzzy で同じことがしたい」と脅されている。xyzzy は普段使ってないから、色の付け方とかわからんのですよ……。
Windows なら EmEditorとかで GUI のメニューから同じようなことができるから、とりあえずそっちでいいんじゃないかなあ。だめ?

2007/08/15

cさんに「索引の読み仮名をひらがなで明示的に指定するのは前時代すぎてあほみたいだ。休み明けまでに何とかしとけ」と恫喝されたので、彼女が休みのあいだに急いで対策することにした。

まあ、実際の cさんはそんなひどい言い方をする人ではなく、ちょうかわいいんだけど、たしかに LaTeX ベースで本を作っていると索引のふりがな入力がうっとうしい。労力の問題だけでなく、原稿にひらがなが氾濫して読みにくくなるという意味でもうっとうしい。

漢字かな変換にはKAKASIを使うのが常套なんだろうな。Gauche には Ruby とちがって KAKASI のライブラリはないけれど、ほとんどジョーカーみたいな c-wrapper がある。
これで libkakasi.h に宣言されているCの関数が Gauche から使える。
(use gauche.charconv)
(use c-wrapper)
(c-load-library "/usr/lib/libkakasi.so.2.1.0")
(c-include "/usr/include/libkakasi.h")

(define (kanji->hira str)
(let ((base-ces "utf-8")
(kakasi-ces "iso2022jp"))
(kakasi_getopt_argv 3 '("kakasi" "-JH" "-p"))
(ces-convert
(x->string (kakasi_do (ces-convert str base-ces kakasi-ces)))
kakasi-ces base-ces)))
KAKASIがUTF-8を扱えないのが厄介だけど、それ以外はとても素直に Gauche で漢字かな変換ができる。

gosh> (kanji->hira "素子")
{もとこ|そし}
さて、この「素子」のようにユニークな読みを決定できない項目があると困っちゃうんだけど、「もとこ」か「そし」かの判断を機械的にすべきではなさそうだ
(「この素子を開発したのは素子さんです」問題)。だから、こういうのだけは人力であらかじめ指定しておくのが最適な対応だと思うのですが、それでかまわないでしょうか?> cさん
こんなふうにマークアップ原稿がLaTeXへと変換されるようにします。
<p>
この素子を開発したのは素子さんです。
<indexterm><i1 sortas="そし">素子<i2>開発者</i2></i1></indexterm>
<indexterm><i1 sortas="もとこ">素子</i1></indexterm>
<indexterm><i1>開発者</i1></indexterm>
</p>

この素子を開発したのは素子さんです。\index{そし@素子!かいはつしゃ@開発者}\index{もとこ@素子}\index{かいはつしゃ@開発者}



参考:
今日の一行::ひらかなのインデックス の cut-sea さんの解
ごとけんさんの ruby-kakasi の kakasi.c

2007/08/10

もう、PostScriptでフィボナッチ数列くらいなら昼休みにコーヒー飲みながらでも考えられる。
/fib {0 1 2 index -1 1 {pop exch 1 index add} for} def

ただ、せっかく PostScript なので、結果を印刷とかもしたい。印刷には show オペレータを使うわけだけど、show は文字列(string)しかとれない。fib オペレータが返すのは数値(integer)なので、文字列の型に変換する必要がある。

PostScript で型を文字列に変換するには、=string cvs とすればいいようだ。
%!
/fib {0 1 2 index -1 1 {pop exch 1 index add} for} def
/Palatino-Linotype findfont 300 scalefont setfont
10 10 moveto
11 fib == =string cvs show
これでページの左下に大きく「89」と印刷される。やっぱりPalatinoフォントの数字は美しい。

fib-11

さらにグラフなど描いてみる。
%!
/fib {0 1 2 index -1 1 {pop exch 1 index add} for} def
20 setlinewidth

1 1 21 { % for
/i exch def
/x 27 i mul def

0 setgray
/Palatino-Linotype findfont 10 scalefont setfont
10 x 5 sub exch moveto
i fib == =string cvs show

/r {rand i mod 21 div} def
r r r setrgbcolor
newpath
x 30 moveto
i fib == 10 div 30 add x exch lineto
stroke
} for

showpage

fib

2007/08/07

PostScript で階乗のつづき。こんどは for 文で。
/func {1 exch -1 1 {mul} for} def
ようするに、自分がしたい操作に必要な変数が、適切な数だけ適切な順番でスタックに積まれているようにすればいいらしい。そしてスタックというやつからは、直前に積んだものだけを取り出すことができる。

たとえば上記で定義した階乗のオペランド func を以下のように呼び出すと、
GS> 5 func
まず 5 がスタックに積まれる。この 5 は「funcへの引数」のつもりなんだけど、スタックから見るとそんなつもりはなくて、ただ値が積まれただけ。次は func を積むんだけど、func は上記のように定義されているので、その定義の一番最初にある 1 がスタックに積まれる。この時点のスタックの状態はこんな感じ。
 1 
---
5
func の定義によれば、次は exch だ。これは、それまでスタックの1番上にあった要素とその下の要素を入れ替える。つまり、スタックの状態はこうなる。
 5
---
1
さらに -1 と 1 を順番に積んで、スタックの状態はこうなる。
 1 
---
-1
---
5
---
1
ここで、本文が mul だけの for が登場する。for というオペレータは、スタックの値を 3つ消費し、それぞれの値を深いほうから順番に「繰り返しの最初」「繰り返しの更新」「繰り返しの終わり」として本文を繰り返す。ただし毎回の繰り返しでは、スタックの先頭に、そのターンにおける変数のようなものが積まれる。こう書くと複雑だけど、ようは最初に本文を実行するときには「5」が、2回目は「4」が、...、5回目は「1」がスタックの先頭に積まれるということ。つまり1回目の繰り返しのとき、スタックの状態はこう。
 5
---
1
本文の mul は、このスタックから値を 2つ取り出して、それらの積をあらためてスタックに積む。したがってスタックの状態は、
5
2回目の繰り返しに際してスタックの先頭に「4」が積まれる。
 4 
---
5
このスタックで mul を適用すると、
 20
3回目の繰り返しに際してスタックの先頭に「3」が積まれる。
 3 
---
20
mul を適用して
 60
4回目の繰り返しに際してスタックの先頭に「2」が積まれる。
 2 
---
60
mul を適用して
120
5回目の繰り返しに際してスタックの先頭に「1」が積まれる。
 1 
---
120
mul を適用して
120
おしまい。こうして最後のスタックの値を取り出せば(そのためには == を使う)、5の階乗の値が得られる。

たぶん用語の使い方はいいかげん。はやく教科書こないかな。

2007/08/06

PostScript が意外におもしろいので真剣に勉強してみようと思う。教科書は、Web で PDF が全部公開されている "Thinking in PostScript" に決めた。書籍はもうとっくに絶版らしい。でも物理的な本が手もとにないとつらいんだよなあ。Amazon マーケットプレイスにも出品されているけどバカみたいに高額なので(6000円以上)、US の同様のサービスに注文した(600円くらい)。まだ届かない。出荷された気配もない。もう待ちきれないよう。

というわけで、試行錯誤しながら階乗を考えてみた。
 /func {dup 1 eq {1 mul} {dup 1 sub func mul} ifelse} def
実行結果。
GS> 10 func ==
3628800
GS> 20 func ==
2.43290202e+18
GS> 100 func ==
inf.0
どうやら再帰的なオペレータの定義ができるらしい。はじめは、ふつうに for を使って解こうとしたんだけど、わかりませんでした。

ところで Emacs の ps-mode は GS のビューワーと連動して出力結果がリアルタイムで見られてすごい。便利すぎ。ただしお絵描きを始めると日付が変わるようだ。

2007/06/23

Schemeの多値の正体は継続

SICPでは「多値」を表立って使うことはない。ただ、5.2.2で2種類のリストをやりとりするときに2変数のプロシージャを使った一見するとトリッキーな処理が登場して、これが実は多値なんだよという種明かしを脚注4でやっている。さらに、そのトリッキーな2変数プロシージャのことを継続(continuation)と呼ぶと説明している。

多値も継続だったのか。

噛みしめるために小さな例を考えてみた。つぎのプロシージャ one は、プロシージャ sincos が返す 2 値を受け取り、その 2 乗和を返す。sincos は引数の角度に対する sin の値と cos の値を返すので、プロシージャ one は引数にどんな数値を指定しても実数の 1 を返す。
(define (one rad)
(receive (sin cos)
(sincos rad)
(+ (* sin sin) (* cos cos))))

(define (sincos rad)
(values
(sin rad) (cos rad)))
このプロシージャ one は、values や receive という組み込みの多値のしくみを使わなくても、つぎのように継続を表すプロシージャ cont を使って多値を模倣できる。
(define (one rad)
(sincos
rad
(lambda (sine cosine)
(+ (* sine sine) (* cosine cosine)))))

(define (sincos rad cont)
(cont (sin rad) (cos rad)))
いちおう実行結果。
gosh>(one 123456)
1.0
gosh>(one -9876)
1.0


まとめ

計算の一部または全部をどうにかしたい場合があって(よその関数に渡したいとか、ちょっと保留しておきたいとか)、そのときの定石は「プロシージャでくるんでしまえ」。で、そういうプロシージャのことを「継続」(continuation)と呼ぶ。

2007/06/18

ついやってしまった。

Functional Programming IAT
関数型指数(潜在的な関数型プログラミングの嗜好度)をはかる IAT
http://dame.dyndns.org/misc/fpiat/


「あなたの関数型指数は 0.62331761674765 です。正が関数型、負が手続き型です。」

でも、設問では"Lisp"(Schemeではない)が関数型言語とされているんだけど、それはどうなの?

2007/06/17

練習問題を放置したままだったSICPの第5章をぽちぽち再開。こうやって再読してみると、すっかりどんな話だったか忘れてる。やっぱり手を動かさないで本を読んでいるだけじゃなんにも身につかない。編集という、まさに読んでいるだけの職業に自分が従事している現実を呪うのはこういう瞬間だ。ただの逆ギレだけど。

というわけで第5章の練習問題に着手しはじめた。ところがすぐに困っちゃったのは、この章の内容がGauche(などのSchemeインタプリタ)で式を実行すれば確かめられる話じゃないこと。とくに5.2節でレジスタマシンのシミュレータをSchemeで作るまでは、紙と鉛筆でデータの流れを図示したりしながら、脳内レジスタマシンを妄想して読み進めるしかない。「レジスタマシンの動作を手でシミュレートしろ」といった問題を考えるのは楽しいんだけど、どうにも「わかった気になっているだけかも」という懸念がぬぐえないのが気持ち悪い。どうでもいいけど、専門書や専門雑誌の編集者を楽しく長く続けていくのに求められる最強のスキルは、「わかった気になったところで留まっていられる」ことだとおもう。前々から気がついてはいたけれど、最近になってひどく実感するようになった。こういう感情をわざわざ書きとめているということは、そういうことだ。ここまでどうでもいい話。

この気持ち悪さはレジスタマシンがあれば解決する。かといって5.2節でシミュレータを作るまで待ってられないし(経験上、1問でも練習問題をぬかすと最後まで練習問題に手をつけずに読了する。帰納法で証明できたらかっこいいかもね)、そもそもSchemeでシミュレートするっていうのも気持ちが悪い。最初にこの章を読んだときは「これは教科書として画期的なアイデアだ」と思ったけど、実際はめんどくささのランクをひとつ繰り上げているだけなような気もする。

アセンブリというわけにもいかないので、Cで書いてみることにした。たとえばフィボナッチ数列の第n項を求めるレジスタマシンのコントローラ(原書512ページのFigure5.12)。
/* Implementation of a recursive fibonachi machine in C.
(Figure 5.12 of "SICP")
2007/6/17
k16.shikano@gmail.com
*/

#include <stdio.h>
#include <stdlib.h>
#define STACKSIZE 100

void fibloop();
void afterfib1();
void afterfib2();
void fibdone();
void immediate();
void rtc(int);

void initstack();
void save(int);
int restore();

int cont = 0;
int val;
int n;

main(int argc, char *argv[])
{
initstack();
n = atol(argv[1]);

fibloop();
}

void fibloop()
{
while(n >= 2)
{
save(cont);
cont = 1;
save(n);
n = n - 1;
}
immediate();
}

void afterfib1()
{
n = restore();
cont = restore();
n = n - 2;
save(cont);
cont = 2;
save(val);
fibloop();
}

void afterfib2()
{
n = val;
val = restore();
cont = restore();
val = val + n;
rtc(cont);
}

void immediate()
{
val = n;
rtc(cont);
}

void fibdone()
{
printf("answer = %d\n", val);
exit(1);
}


/* return to continue */
void rtc(int c)
{
switch(c)
{
case 0: fibdone();
case 1: afterfib1();
case 2: afterfib2();
}
}


/* naive stack implementation */
int stack[STACKSIZE];
int *pstack;
int *pinit;

void initstack()
{
pstack=stack;
pinit=pstack;
}

void save(int val)
{
if (pstack > pinit+STACKSIZE){
perror("Reached Stack End");
exit(1);
}

*pstack=val;
++pstack;
}

int restore()
{
if (pstack == pinit){
perror("Reached Stack Head");
exit(1);
}

--pstack;
return *pstack;
}
スタックの機能をでっちあげて、Figure5.12にあるSchemeっぽい式をもじどおりCに置き換えただけ。コンパイルして実行すると(想定内のセグメンテーション違反はおこすけど)あっさりうごく。
k16@debian:~/play $ ./fib 24
answer = 46368
k16@debian:~/play $ ./fib 25
セグメンテーション違反です

これは、Cでフィボナッチを書きました、という話ではなく、これまでSchemeというレイヤでプログラミングしているときに再帰関数だと思っていたコレが、
(define (fib n)
(if (< n 2)
n
(+ (fib (- n 1)) (fib (- n 2)))))
その下のレイヤでは上記のCのコードのようなレジスタへの代入とスタックへの出し入れだけの形(現代のコンピュータがかろうじて完璧に扱える形)に解くほぐせて、しかも動く、という話なんだよね。プログラミング言語っていうのは、もしかして、ここで手動で試してみたコードからコードへの変換みたいな処理を自動的に行うしくみのことなのか?

なんだかこの本を読みはじめたときのようなわくわく感が。

2007/05/31

地球の裏側までトンネルを掘ってボールを落としたらどうなるかという話題になった。空気がなくて、他の天体の重力の影響を無視できて、トンネルが地球の重心を通る直線で、トンネルの壁が重力によって押しつぶされなくて、なんかほかにもいろいろ条件を満たすなら、ボールは反対側の地表付近まで届く。トンネルの途中でぴたっと止まっちゃうことはない。力学は昔も今もさっぱり自信がないんだけど、その理由を文章で説明するとしたらこんな感じになると思う。
ボールはトンネルの真ん中まで落ちるあいだ、つねに一定の加速度で移動する。
つまり、どんどん速度が増える。
トンネルの真ん中付近を突っ切るときが最速で、そこから先は正反対の向きの加速度で移動する。
つまり、だんだんゆっくりになる。
そのうち前半の行程で得たエネルギーを使い果たし、反対側の地表付近で一瞬静止して、今度はもときた方向へ落ちていく。
以下繰り返し。

で、こういう問題を考えるときは「地球の中心に全質量が集中している」と見たてることになっているわけだけど、その理由を説明するのがむずい。この場合の「見たて」は、たとえば数学で「0.99999… = 1」とか規定するのと違って、そう考えると議論に都合がいいからという性質だけのものじゃなく、もっと本質的な話だったはず(もちろん、どんな理学的な説明だって「そのほうが都合がいいから」って言い方はできるんだろうけど……)。で、昼休みにWikipediaを見てみたら、あっさり証明がのっていた。(読んではいない)

Shell theorem
http://en.wikipedia.org/wiki/Newton%27s_sphere_theorem

もしボールがトンネルを移動している最中に球に地球が真っ二つに割れたら?という話も出たけど、それまでにボールが得ている運動エネルギーと変化した周囲の重力から得るエネルギーとが均衡するように動くとしか……(実際には地球を真っ二つに割ることになった外因からのエネルギーがいちばん大きく影響するんじゃない?)

2007/05/28

パズル「グリッド色分け問題」少し改良版

「組合せ最適化」をぱらぱらめくったけど安直な方法が見つけられなかったので、オクトーバーフェストに行ってビールをのみながらほげほげ考えていたら、行ごとに組み合わせを求めつつ枝刈りして、それを枝刈りしながら列に集めれば、ずいぶんメモリを省略できるはずだと思いついた。実際これはうまくいって、4x4程度なら瞬時に計算できる。

たとえば10番目に得られた結果。10番目であることに特に意味はない。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


(use srfi-1)
(use util.stream)

(define (line-colorings width colors pred)
(cond ((= 1 width)
(apply stream (zip (iota colors colors -1))))
((= 0 colors)
stream-null)
(else
(let R ((top colors))
(if (= 0 top)
stream-null
(stream-append
(stream-filter
pred
(stream-map (cut cons top <>)
(line-colorings (- width 1) colors pred)))
(R (- top 1))))))))

;; (stream of lists) -> (stream of lists)
(define (grid-colorings hight rows patterns pred)
(cond ((= 1 hight)
patterns)
((stream-null? patterns)
stream-null)
(else
(let R ((top (stream-car patterns))
(rest (stream-cdr patterns)))
(stream-append
(stream-filter
pred
(stream-map (cut append top <>)
(grid-colorings (- hight 1) rows patterns pred)))
(if (stream-null? rest)
stream-null
(R (stream-car rest) (stream-cdr rest))))))))

(define (tiles lines rows colors)
(stream-filter
(cut egalite? <> (quotient (* lines rows) colors) colors)
(grid-colorings lines rows
(line-colorings rows colors
(cut stripe? <>))
(cut staggered? <> rows))))


;;;; predicates
; Doesn't the list contain adjacent cells with same color?
(define (stripe? ls)
(cond ((null? ls)
#t)
((null? (cdr ls))
#t)
(else
(let ((v (car ls))
(w (cadr ls)))
(and (not (= v w))
(stripe? (cdr ls)))))))

; Doesn't the list contain vertically adjucent cells with same color?
(define (staggered? ls rows)
(let R ((rows (transpose ls rows)))
(if (null? rows)
#t
(and (stripe? (car rows))
(R (cdr rows))))))

; Does each color appear at least n times?
(define (egalite? ls n c)
(let R ((c c) (ls ls))
(cond ((= c 0) #t)
((< (length ls) n) #f)
(else
(receive (the-colors rest)
(partition (cut = c <>) ls)
(and (>= (length the-colors) n)
(R (- c 1) rest)))))))



;;;; some list utils
(define (group ls n)
(receive (front end)
(split-at ls n)
(if (null? end)
(list ls)
(cons front (group end n)))))
(define (transpose ls rows)
(apply zip (group ls rows)))

2007/05/26

パズル「グリッド色分け問題」をSchemeで解く

自宅のトイレにはディック・ブルーナのポスターがもう何年も張ってある。全体がグリッドに仕切られていて、そのひとつひとつに彼の代表作から抜き出した絵が並べられてるんだけど、そのうちの1枚に描かれているテーブルクロスの柄が気になってしょうがない。

R0011516

どうしてこのテーブルクロスは、きちんと格子が塗り分けられていないんだろう。左下で青いマスが横に並んじゃっているのがどうにも気持ち悪い。行列の成分でいうと33と34の2つ。もしスプーンがおかれてなかったら境界が識別できないじゃないか。4x4のグリッドを3色で塗り分けるパターンなんていくらもあるだろうに。

というわけでパターンを求めてみる。

戦略は、まず塗り分けのパターンをすべて求めて(つまり隣同士が同じ色に塗られるパターンを含む)、そのうちで3色をちゃんと使ってうまく塗り分けられているものを取り出す。

「3色をちゃんと使ってうまく塗り分けられている」はどう評価しよう? 
とりあえず、どの色も少なくとも5回(16÷3)は使われていて、隣同士が違う色になっていればよしとしよう。(デザイン上のよしあしを評価するとしたら何を考えたらいい?)

4x4のグリッドを塗り分けるパターンは、長さ16のリストであらわすことにする。使う色は1,2,3という数値で表す。つまり、リストの各要素には、各マスの色を意味する1,2,3のいずれかの数値がはいる。これをグリッドの左上→右下という順番で並べる。たとえば以下のような塗り分けは、(1 3 2 3 3 2 1 2 1 3 2 1 3 2 1 3) というリストであらわすことにする。( 1:青、2:オレンジ、3:緑)

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


隣同士が別の色で塗り分けられているかどうかは、ベタに縦横の関係を調べる patch? というプロシージャを定義して、それで済ませることにする。
(define (stripe? ls)
(cond ((null? ls)
#t)
((null? (cdr ls))
#t)
(else
(let ((v (car ls))
(w (cadr ls)))
(and (not (= v w))
(stripe? (cdr ls)))))))
(define (patch? ls n m)
(and (let L ((lines (group ls n)))
(if (null? lines)
#t
(and (stripe? (car lines))
(L (cdr lines)))))
(let R ((rows (transpose ls n)))
(if (null? rows)
#t
(and (stripe? (car rows))
(R (cdr rows)))))))
ところでgroupとtransposeは、いつも使いたいときに一瞬探すんだけど見つからなくて、そのたびに下手な実装をしてる気がする。今回はこんなんで。
(define (group ls n)
(receive (front end)
(split-at ls n)
(if (null? end)
(list ls)
(cons front (group end n)))))
(define (transpose ls rows)
(apply zip (group ls rows)))

塗り分けパターンをすべて求めるにはどうしたらいいか。

たとえば、3つのマスを3色で塗り分けるやり方をすべて求めることを考えてみよう。以下のようなマスA,B,Cを緑黒赤の3色で塗り分けるパターンをすべて求めたい。

A
B
C


いま、都合よく R という関数があって、これを使うと2マスを3色で塗り分けパターンが全部求められるとしよう。R を使えば、以下の 3つの結果をよせ集めることで、3マスの塗り分けパターンを求めることができる。
  1. Aを緑に塗って、BとCは R に従って塗り分ける全パターン
  2. Aを黒に塗って、BとCは R に従って塗り分ける全パターン
  3. Aを赤に塗って、BとCは R に従って塗り分ける全パターン
ようするに、うしろの塗り分け方さえ全部求まってれば、先頭の色だけとっかえひっかえした結果をよせ集めることで、全体の塗り分けがすべて得られる。

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


このアイデアをストリームを使ってダイレクトにコードにするとこんな感じ。Scheme だと簡単すぎ。
(use srfi-1)
(use util.stream)

(define (gen-colorings width colors)
(cond ((= 1 width)
(apply stream (zip (iota colors colors -1))))
((= 0 colors)
stream-null)
(else
(let R ((top colors))
(if (= 0 top)
stream-null
(stream-append
(stream-map (cut cons top <>)
(gen-colorings (- width 1) colors))
(R (- top 1))))))))
あとは、先に定義した patch? を使ってストリームをフィルタリングすればいい。どの色もだいたい平等に塗られているかどうかを調べるプロシージャ egalite? も定義しておいて、ここであわせてフィルタリングする。

(define (tiles n m c)
(stream-filter
(lambda (tone)
(and
(egalite? tone n c)
(patch? tone n m))
(gen-colorings (* n m) c)))

(define (egalite? ls n c)
(let R ((c c) (ls ls))
(cond ((= c 0) #t)
((< (length ls) n) #f)
(else
(receive (the-colors rest)
(partition (cut = c <>) ls)
(and (>= (length the-colors) n)
(R (- c 1) rest)))))))
これで (tiles 4 4 3) とかって実行すれば、塗り分けパターンを順次計算してくれるストリームが帰ってくる。はずなんだけど、実際には組み合わせが爆発しちゃう。16マスを3色に塗り分けようと思ったら 316 = 43,046,721 のパターンがありうるわけで、すべての塗り分けパターンからなるリストを作ったりすると巨大なリストになって身動きが取れなくなると思ってストリームを使ってみたんだけど、どのみち4x4=16マスの3色塗り分けを全部求めるのは無理だったもよう。
しかたがないので、問題を1行小さくして 3x4 の横長のテーブルクロスの3色塗り分けを求めてお茶を濁すことにした。
gosh> (define s (tiles 4 3 3))
s
gosh> (stream->ref s 1)
(3 2 1 3 2 1 3 2 1 2 1 3)
gosh> (stream-ref s 2)
(3 2 1 3 2 1 2 1 3 2 1 3)
gosh> (stream-ref s 3)
(3 2 1 2 1 3 2 1 3 2 1 3)
gosh>
最初に得られる結果を先の色定義( 1:青、2:オレンジ、3:緑)で塗り分けると、こうなる。

 
 
 
 
 
 
 
 
 
 
 
 


ついでに、このHTMLテーブルを描くのにでっちあげた補助関数。
(define (tile->html tile rownum)
(define (num->color n)
(cond
((= 1 n) "blue")
((= 2 n) "orange")
((= 3 n) "green")
(else "black")
))
(define (line->str line)
(string-append
"<tr>\n "
(apply string-append
(map (lambda (cell)
(string-append "<td style=\"background-color:"
(num->color cell)
"\"><div style=\"width:1em\">&nbsp;</div></td>"))
line))
"\n</tr>\n"))
(define (lines->str lines)
(string-append "<table>\n"
(apply string-append (map (cut line->str <>) lines))
"</table>\n"))
(lines->str (group tile rownum)))
しかし、tilesの引数は順番を逆にするべきだったな。行と列がいれかわってて紛らわしいことこのうえない。

2007/05/18

XMLっぽい構造をパースする

指定した範囲の内側でだけ、テキストのパターン置換をしたい。つまり、こういうことがしたい。
gosh> str
"<title>
<en>Introduction</en>
<ja>は じ め に</ja>
</title>
<p>
<en>
Here we'll discuss about english
<footnote>
<p class="footnote">
Or, any language you speak as a native tongue.
</p>
</footnote>
.
</en>
<ja>
ここでは日本語
<footnote>
<p class="footnote">
で な く て も、母 国 語 な ら な ん で も い い。
</p>
</footnote>
について説明しよう。
</ja>
</p>"


gosh> (regexp-replace-all-among-all 'ja #/\b\s\b/ str "")
"<title>
<en>Introduction</en>
<ja>はじめに</ja>
</title>
<p>
<en>
Here we'll discuss about english
<footnote>
<p class="footnote">
Or, any language you speak as a native tongue.
</p>
</footnote>
.
</en>
<ja>
ここでは日本語
<footnote>
<p class="footnote">
でなくても、母国語ならなんでもいい。
</p>
</footnote>
について説明しよう。
</ja>
</p>"


先日のような邪道な試行錯誤をしたり、再帰下降パーザについて少し勉強したりした結果、地道にパーズするのがいちばんだということがよく分かった。PerlやJaveなら優れたXML処理のライブラリを使うべきなのかもしれないね。

置換をほどこしたい領域(上の例では<ja>...</ja>の部分)を取り出すことから考えよう。その前に、XMLっぽいテキストを構成するパーツを先頭から順番に取り出してくれるプロシージャ read-xml があると仮定する。read-xml を一回呼ぶと、「<title>」や「<p class="footnote">」のようなタグ、もしくは、タグの前後の本文を、テキストの先頭から順番にゲットできる。こんな感じ。
(with-input-from-string "aaa<p>bbb</p>ccc"
(lambda ()
(read-xml) ; ⇒ aaa
(read-xml) ; ⇒ <p>
(read-xml) ; ⇒ bbb
(read-xml) ; ⇒ </p>
(read-xml) ; ⇒ ccc
))

この read-xml でひとつずつパーツを取り出してチェックしていく。探している領域を開始するタグ(いまの例では<ja>)が取り出せたら、終了を表すタグ(いまの例では</ja>)が現れるまで次々にパーツをつないでいくことで、ほしい領域が取り出せる。領域がネストしている可能性もあるので、開始タグの数もチェックしておくようにする。ざっくり書くとこんな感じ。利便性を考えて、領域の前後の文字列も返すようにした。
(define (xml-maximal-region tagname)
(define (xmltag? e)
(and (> (string-length e) 1)
(char=? #\< (string-ref e 0))))
(define (tag->name e)
(string-trim-right
(x->string (string-drop e 1))
#\>))
(define (start-tag? e)
(and (xmltag? e)
(equal? (x->string tagname)
(tag->name e))))
(define (end-tag? e)
(and (xmltag? e)
(equal? (x->string tagname)
(string-drop (tag->name e) 1))))
(define (rest-xml)
(let R ((next (read-xml)))
(if (string-null? next)
""
(string-append next (R (read-xml))))))
(define (in-region e body c before)
(cond ((string-null? e)
(error "Premature end of input -- GET-XMLTAGGED-MAXIMAL-REGION"))
((end-tag? e)
(if (= 0 c)
(values before (string-append body e) (rest-xml))
(in-region (read-xml) (string-append body e) (- c 1) before)))
((start-tag? e)
(in-region (read-xml) (string-append body e) (+ c 1) before))
(else
(in-region (read-xml) (string-append body e) c before))))
(define (out-region e body before)
(cond ((string-null? e)
(values before "" ""))
((start-tag? e)
(in-region (read-xml) e 0 before))
(else
(out-region (read-xml) body (string-append before e)))))
(out-region (read-xml) "" ""))

この xml-maximal-region を使えば、求めるプロシージャ regexp-replace-all-among-all が簡単に定義できる。
(define (regexp-replace-all-among-all region-declaration rx str sub)
(with-input-from-string str
(lambda ()
(receive (before region after)
(xml-maximal-region region-declaration)
(string-append before
(regexp-replace-all rx region sub)
(if (string-null? after)
after
(regexp-replace-all-among-all region-declaration rx after sub)))))))
あとは read-xml を書けばいい。むずかしいところはないけど面倒。長いので、全体とあわせて下記を参照。

replace-among.scm


References

「なんでも再帰」Shiro Kawai(2003/1)
http://www.shiro.dreamhost.com/scheme/docs/tailcall-j.html

『Perl & XML』:Erik T. Ray,Jason McIntosh(2002/11)
http://www.amazon.co.jp/dp/4873111064

2007/04/30

二分木が描きたい。

Scheme を使っていると木を使うことが多い(Scheme に限らないけど)。教科書なんかには整形された木の絵がよく出てくるけど。あれはみんなどうやって描いているんだろう。きっと PostScript のコマンドを生成したりして描いているに違いない。そこで、再帰下降パーザの例としてありがちな四則演算を表す木を描くのに挑戦してみた。

arithmetic-culc-tree.scm
$ gosh arithmetic-culc-tree -tree ps
1*2+3*((4+5)-7)/(8+9)
%!
<< /PageSize [460.0 350.0] >> setpagedevice
newpath
175.0 297.0 moveto
85.0 270.0 lineto
175.0 297.0 moveto
265.0 270.0 lineto
...(以下略)

convert で png に変換した結果。

もうちょっと見ための改善の余地がありそうだけどもうつかれた。

2007/03/30

Gaucheの単体テストで、副作用により標準出力に書き出す処理(displayとか)の動作をテストしたい。つまり、こんな単体テストをしたい。
(test* "display test"
"foobar string"
(display "foobar string"))
もちろんこれは失敗する。関数 test は equal? で第2引数と第3引数を比較するだけだから。オプション引数を与えて比較に使うプロシージャを変更することもできるけど、そもそも上記のような display のテストでは第3引数を評価した値が # でしかないので、テストの意味をなさない。

display の動作を脳内シミュレートすると、こんなふうに出力ポートを曲げればうまくいきそう。
(test* "display test"
"foobar string"
(with-output-to-string (lambda () (display "foobar string"))))
どうやらうまくいく。あとはこんなマクロをでっちあげておけばうれしい。
(define-syntax test-with-output*
(syntax-rules ()
((_ e1 e2 e3)
(test* e1 e2 (with-output-to-string (lambda () e3))))
((_ e1 e2 e3 e4)
(test* e1 e2 (with-output-to-string (lambda () e3)) e4))))
結果。
gosh >(test-with-output* "display test"
"foobar string"
(display "foobar string"))

test global conversion 2, expects "foobar string" ==> ok
#<undef>

2007/03/24

文字列を螺旋にそって描く。これに似ているけど、もっと単純に、ASCIIのみからなる文字列を同心円状の渦巻に沿って出力する。交差はさせない。(できない)

howm wiki - spiral.el
http://howm.sourceforge.jp/cgi-bin/hiki/hiki.cgi?SpiralDotEl

昨日定義した関数たちを改良して、
  • 螺旋のパラメータを指定できるようにする
  • state の列に真偽値ではなく各文字を対応させる(同じ座標の state を飛ばす処理も必要)
ようにする。
;; num -> direction -> type -> [state]
(define (make-spiral-states length start-x start-y start-direction l-or-r)
(define (make-spiral-series total)
(let* ((most (floor (/ (- (sqrt (+ 1 (* 8 (- total 1)))) 1) 2)))
(lmost (iota (- most 1) 2 1))
(rest (- total (fold + 1 lmost))))
(append (list 2) lmost (if (zero? rest) '() (list rest)))))
(let ((series (make-spiral-series length))
(init-state (make-state start-x start-y start-direction))
(turn (if (equal? l-or-r 'left) turn-left turn-right)))
(scanf init-state (concatenate (map (lambda (n) (append (keep-moving n) (list turn))) series)))))

;; [state] -> [codes]
(define (normalize states)
(let R ((ps '()) (codes (map car states)))
(cond ((null? codes)
ps)
((member (car codes) ps)
(R ps (cdr codes)))
(else
(R (cons (car codes) ps) (cdr codes))))))

;; [codes] -> [state]
(define (stick-char codes chars)
(map (cut list <> <>) codes chars))

;; [state] -> [[char]]
(define (bitmap ps)
(define (range xs)
(list-ec (: i (apply min xs) (+ (apply max xs) 1)) i))
(define (corresponding-char code ps)
(cond ((null? ps) " ")
((equal? code (caar ps)) (cadar ps))
(else (corresponding-char code (cdr ps)))))
(let ((codes (map car ps)))
(list-ec (: x (range (map car codes)))
(list-ec (: y (range (map cadr codes)))
(corresponding-char (list x y) ps)))))

;; [[char]] -> string
(define (picture bitmap)
(string-join
(map (lambda (line) (string-join (map x->string line) "" 'strict-infix))
bitmap)
"\n" 'strict-infix))


(define (display-spiral string)
(let ((length (string-length string)))
(display
(picture
(bitmap
(stick-char
(normalize (make-spiral-states length 0 0 2 'left))
(string->list (string-join (string-split string #[\s]) "+" 'strict-infix))))))
(newline)
(values)))
実行例(サンプルの文字列は Gauche のトップページから引用)
gosh> (display-spiral "Gauche is an R5RS Scheme implementation developed to be a handy script interpreter,
which allows programmers and system administrators to write small to large scripts for their daily chores.
Quick startup, built-in system interface, native multilingual support are some of my goals.")


Gauche+
i
nterpreter,+which+all s
i o +
+ all+to+large+scri w a
t m p s n
p s ,+built-in+sy t + +
i + p s s p R
r e u ingual+su t + r 5
c t t l p e f o R
s i r i +my+g p m o g S
+ r a t f o o + r r +
y w t l o a r i + a S
d + s u + .sl t n t m c
n o + m e + t h m h
a t k + mos+era e e e e
h + c e r i r m
+ s i vitan+,ecaf r s e
a r u + + +
+ o Q+.serohc+yliad a i
e t n m
b artsinimda+metsys+d p
+ l
ot+depoleved+noitatneme


スクリプトの全体→ spiral-string.scm

2007/03/23

R.バード『関数プログラミング』の亀の子図形問題、Scheme版

『関数プログラミング』(R. バード,P.ワドラー著/武市 正人訳)に亀の子図形の例題がある。その一筆書きバージョンを Gauche で書くとこんな感じ。
;; state -> state
(define (move state)
(match state
(`((,x ,y) 0) (make-state (- x 1) y 0)) ;; N
(`((,x ,y) 1) (make-state x (- y 1) 1)) ;; W
(`((,x ,y) 2) (make-state (+ x 1) y 2)) ;; S
(`((,x ,y) 3) (make-state x (+ y 1) 3)) ;; E
))

;; state -> state
(define (turn-left state)
(let-state state
(x y d)
(make-state x y (remainder (+ d 1) 4))))
ただし、
(define (make-state x y d)
(list (list x y) d))

(define-syntax let-state
(syntax-rules ()
((_ e1 (e2 e3 e4) e5 ...)
(let ((e2 (caar e1))
(e3 (cadar e1))
(e4 (cadr e1)))
e5 ...))))
とする。

この move と turn-left(および同様に定義した turn-right)を使って「鉛筆の軌跡」をつくり、それを適当な大きさのビットマップに対応させれば、結果として絵が描ける。そのためのプロシージャは、たとえば以下のように定義すればいい。
;; [state] -> [[boole]]
(define (bitmap-by-truth-value ps)
(define (range xs)
(list-ec (: i (apply min xs) (+ (apply max xs) 1)) i))
(define (orlist ls)
(cond ((null? ls) #f)
((car ls) #t)
(else
(orlist (cdr ls)))))
(define (in? x xs)
(orlist (map (cut equal? <> x) xs)))
(let ((codes (map car ps)))
(list-ec (: x (range (map car codes)))
(list-ec (: y (range (map cadr codes)))
(in? (list x y) codes)))))

;; [[boole]] -> string
(define (picture-with-numbermark bitmap)
(string-join
(map (lambda (y)
(string-join (map (lambda (x) (if x "#" " ")) y) "" 'strict-infix))
bitmap)
"\n" 'strict-infix))
(ちなみにコード中にコメントで示している型は厳密なものではなく、コードを書くときの便宜的なものです。)

教科書には正方形を描く例がある。でもそれは面白くない。簡単な応用として螺旋模様を描いてみよう。
(define (make-simple-spiral-states length)
(define (make-spiral-series total)
(let* ((most (floor (/ (- (sqrt (+ 1 (* 8 (- total 1)))) 1) 2)))
(lmost (iota (- most 1) 2 1))
(rest (- total (fold + 1 lmost))))
(append (list 2) lmost (if (zero? rest) '() (list rest)))))
(let ((series (make-spiral-series length))
(init-state (make-state 0 0 0)))
(scanf init-state (concatenate (map (lambda (n) (append (keep-moving n) (list turn-left))) series)))))
keep-moving と scanf は以下のような関数。
;; num -> (state -> [state])
(define (keep-moving n)
(list-tabulate n (lambda (i) move)))

;; (alpha -> beta) -> gamma -> [alpha -> beta]
(define (scanf init procs)
(if (null? procs)
'()
(let ((value ((car procs) init)))
(cons value
(scanf value (cdr procs))))))

実行するとこんな感じ。
gosh> (display (picture-with-numbermark (bitmap-by-truth-value (make-simple-spiral-states 55))))
###########
#
####### #
# # #
# ### # #
# # # # #
# # # #
# ##### #
# #
##########<undef>
(srfi-1 と srfi-42 と util.match が必要)

2007/03/20

数学的帰納法は超限帰納法により証明します。超限帰納法は整列集合の性質から導かれます。たしかに大学の集合論の授業でこれを最初に知ったときはびっくりした。
うすうす感じてたんだけど、どうやら僕の生き方は「むかつき駆動」らしい。一方、人生というのは基本的に前進するほど問題が増えるので、むかつく→対応→別なことにむかつく→対応→……。


たぶん「むかつき駆動」はハッカーの定義なんかにも重なるんだと思う。でも僕はもちろんハッカーではない。むかつくポイントが彼らとは異なると思われるから。たとえば、計算機に仕事をさせられていることにむかつくことはなくて、計算機を使えていない自分にむかつく。よく言えば客観的だけど、ええかっこしいなのかもしれない。だとしたらむかつく。でもええかっこしいかもしれない自分にむかついて誤った対応(自分の外面に無頓着になるとか)をすると女の子に嫌われそうなのでやっぱりむかつく。むかー☆

2007/03/16

開始タグと終了タグで構造化されているテキストがあって、しかも同じ種類のタグが入れ子になっていたら、どうやって一番外側のグループを取り出すのが定石なんだろう。『詳解 正規表現』には何かカッコいい方法が書いてあるんだろうか。

とりあえず問題をブレークダウンすると、こんな風に入れ子になっているパターンを正規表現でうまく補足できないかなあという話。
gosh> str
"<p>This is a paragraph.
<footnote><p>Here is a paragraph in the footnote</p></footnote>
Here is a main paragraph again.</p>"

gosh> ((rxmatch #/うまいパターン/ str))
"<p>This is a paragraph.
<footnote><p>Here is a paragraph in the footnote</p></footnote>
Here is a main paragraph again.</p>"
もちろん、これじゃ困るわけ。
gosh> ((rxmatch #/<p>.*?<\/p>/ str))
"<p>This is a paragraph.
<footnote><p>Here is a paragraph in the footnote</p>"

結論。一晩寝ても「うまいパターン」は見出せなかった。というわけで、正規表現ではなくGaucheでなんとかする方向へ逃げる。

戦略としては、こんなでどうか。
  1. 開始タグをみつける。
  2. 開始タグから最初にみつけた終了タグまでを match 候補にする。(その途中で開始タグを見つけるかもしれないけど気にしない)
  3. match 候補のなかの開始タグと終了タグの数を調べる。同じなら match 候補を match にして終了。違ったら4.へ。
  4. match 候補を開始タグとみなして2.へ。
実装。
; matche to the broadest range sandwiched between two patterns
(define (rxmatch-between-pattern pattern1 pattern2 string)
(define (make-pattern pattern-string1 pattern-string2)
(string->regexp
(string-append pattern-string1 ".*?" pattern-string2)))
(define init-pattern
(make-pattern (regexp->string pattern1) (regexp->string pattern2)))
(define (num-of-matched-inner pattern string)
(let R ((head-matched (pattern string)) (n 0))
(if (not head-matched)
n
(R (pattern (rxmatch-after head-matched)) (+ n 1)))))
(let R ((matched (init-pattern string)))
(if (not matched)
#f ;; There's no match -- REGEXP-BETWEEN-PATTERN"
(if (= (num-of-matched-inner pattern1 (matched))
(num-of-matched-inner pattern2 (matched)))
matched
(R ((make-pattern (regexp-quote (matched)) (regexp->string pattern2)) string))))))
これの問題点は、異常に長いグループがあった場合に Gauche の "regexp too large." エラーが出ることだ。はてどうする?

2007/03/14

おとといの anagram は、できるだけ奇妙な reverse を考えているときに気がついた。
(define (my-reverse ls)
(append (if (null? (cddr ls))
(cdr ls)
(my-reverse (cdr ls)))
(list (car ls))))

むかしむかし reverse に悩んでいたのは、もう2年も前なのか。

2007/03/12

回文を作りたい。いや、別に回文を作りたいわけじゃないんだけど、こういう再帰もできるのかと今頃気がついてなんとなく楽しくなった22時。
(define (anagram ls)
(append (list (car ls))
(if (null? (cddr ls))
(cdr ls)
(anagram (cdr ls)))
(list (car ls))))
gosh> (anagram '(ABLE WAS I ERE))
(ABLE WAS I ERE I WAS ABLE)
例文は『On Lisp』より。

2007/03/08

連番を作るのに integ のようなプロシージャを使うのはいいけど、Schemeでは引数の評価順が規定されていないので連番の順序はどうなるか保証はないよという話。次のようなコメントをいただいたので、これを復習してきちんと消化しよう。
ところで、

gosh> (list (integ) (integ) (integ))
(1 2 3)

は,R5RSには引数の評価順が規定されていないので,これが
(3 2 1)になっても文句はいえないと思う:)

そういえばSICPの第3章にもそんな問題(ex. 3.8)があった。評価順が左→右の実装では (+ (f 0) (f 1)) が 0 だけど、右→左の実装では 1 になるような f を定義しろという問題。

その昔、この問題を解いたときに定義したのはこんなグローバル変数を使った方法だった。
(define y 0)
(define (f x)
(if (= y 0)
(begin (set! y x) 0)
(begin (set! y 1) y)))
これはダサい。いまならこう書く(えらそう)。
(define f
(let ((y 0))
(lambda (x)
(if (= y 0)
(begin (set! y x) 0)
(begin (set! y 1) y)))))

さて、ふつうの実装は左→右で評価するので、これを試すには右→左で評価してくれる実装が必要だ。まあ、この問題だけなら引数の順番を入れ替えて (+ (f 0) (f 1)) と (+ (f 1) (f 0)) を Gauche で試せばいいんだけど、せっかくSICPの第4章でメタ言語評価器(つまりSchemeで定義するSchemeのインタプリタ)を作るので、それを右→左で評価するように改造して使うことにする。それに、ex. 4.1が、まさにそのように改造せよという問題だ。ここではletも必要なので、letを追加したdata-directed スタイルのバージョン(つまりex. 4.3と4.6の成果を取り込んだもの)を使う。たぶんここら(SICP Web Site for the Japanese Edition)あたりにもあると思うけど、オレ実装は以下(getとputが必要)。

metacircular-evaluator-right-to-left.scm

gosh> 
(define f
(let ((y 0))
(lambda (x)
(if (= y 0)
(begin (set! y x) 0)
(begin (set! y 1) y)))))

f
gosh> (+ (f 0) (f 1))
0
gosh> (driver-loop)

;;; M-Eval input:
(define f
(let ((y 0))
(lambda (x)
(if (= y 0)
(begin (set! y x) 0)
(begin (set! y 1) y)))))


;;; M-Eval value:
ok

;;; M-Eval input:
(+ (f 0) (f 1))

;;; M-Eval value:
1

もちろん(list (integ) (integ) (integ))も文句を言えない結果に。
;;; M-Eval input:
(list (integ) (integ) (integ))

;;; M-Eval value:
(3 2 1)

2007/03/07

call/ccは「引数を1つとる関数」である。
call/ccは「引数を1つとるプロシージャを1つ引数にとる関数」である。
ということは、call/ccにcall/ccを引数として与えられる。
(call/cc call/cc)
これは何か。

おさらいから。call/ccは引数を1つとるプロシージャを引数にとる。
(call/cc (lambda (k) ...))    ; (A)
これをなんとなく評価してみると、内側の(lambda (k) ...)に適当な引数を与えて評価されたかのような結果が得られる。
gosh> (call/cc (lambda (k) 1))
1
gosh> (call/cc (lambda (k) (odd? 1)))
#t
このときの適当な引数が継続である。つまり、(A)を評価するとそのときの継続を引数にして内側の(lambda (k) ...)が評価される。これがcall/ccという名前の関数の動作だ。

では、そのときの継続適当な引数)ってのが具体的に何であるかを考えよう。実際に内側の(lambda (k) ...)でkを返すようにしてみても、あまり有効なヒントは得られない。
gosh> (call/cc (lambda (k) k))
#<subr continuation>
そこで、call/ccの引数であるプロシージャのなかで、kを適当なグローバル変数に代入してみる。
gosh> (define c '())
c
gosh> (call/cc (lambda (k) (set! c k) k)) ; (B)
#<subr continuation>
gosh> (c 100 "abc" (odd? 1) (display (/ 5 2)) (display "\n"))
2.5
100
"abc"
#t
#<undef>
#<undef>
どうやらcは、任意の引数をとってそれを評価するプロシージャみたいに機能している。しかしcはプロシージャではない。cとeq?の意味で同じオブジェクトであり、かつ(B)が返すはずのkも、やはりプロシージャではない。だから、こんなふうに適用することはできない。
gosh> ((call/cc (lambda (k) (set! c k) k)) 1)    ; (C)
ERROR: invalid application: (1 1)
(C)からは、kがプロシージャでないことを納得する以上に興味深いことがわかる。もしcall/ccが内側のlambdaを評価しているだけなら、(C)のように実行してエラーが返ったところで、cには(B)を評価したときと同じような動作をするオブジェクトが代入されているはずだ。ところが結果は次のとおり。
gosh> (c 100)
ERROR: invalid application: (100 1)
どうやらc(つまりk)は、外側のcall/ccがどういう文脈で評価されたかを知っている。そしてSchemeでは、そういうオブジェクトを継続と呼んでプロシージャ並みに自由に扱うことができる。

(C)の場合、call/ccの返すオブジェクトは、引数の位置に数字の1をともなって評価されようとしている((call/cc ...) 1)、引数自身(lambda (k) ... k))だ。
だから、(C)は「引数の位置に数字の1をともなって評価されようとしている数字の1」だし、(c 100)は「引数の位置に数字の1をともなって評価されようとしている数字の100」だ。いずれもエラーになって当然。

ちなみに、cは「引数の位置に数字の1をともなって評価されようとしている……」なので、次のようにcを評価すればエラーにならない。
gosh> (c (lambda (x) 1))
1
gosh> (c (lambda (x) 100))
100
もちろん最初から同様にやることもできる。
gosh> ((call/cc (lambda (k) (set! c k) k)) (lambda (x) 1))    ; (D)
1
(lambda (x) 1)とかを引数にしている限り、cの動作もあんまり変わらない。
gosh> (c (lambda (x) 1))
1
gosh> (c (lambda (x) 100))
100
しかし、この見た目に惑わされると道を見失う。(D)の結果cは「引数の位置に数字の1を返す1引数プロシージャをともなって評価されようとしている……」になるので、先のcとはまったく異なるものだ。今度のcには、「1引数プロシージャを引数にするプロシージャ」を適用できる。
gosh> (define apply100
(lambda (proc)
(proc 100)))

apply100
gosh> (c apply100)
1
くどく書けば、(c apply100)は「引数の位置に数字の1を返す1引数プロシージャをともなって評価されようとしているapply100」だ。だから1が返る。

ところで「1引数プロシージャを引数にするプロシージャ」は、わざわざapply100みたいなのを定義しなくても身近にある。call/ccだ。ということは、次の式もきちんと評価されるし、その結果もここまでくれば明らかだよね。
gosh> (c call/cc)
1


さて。

冒頭に書いたように、call/ccは引数を1つとるプロシージャを引数にとる。そこで今度は、さっき定義したapply100を使って次の式について考えてみよう。
(call/cc apply100)    ; (E)
apply100は、「引数を1つとって、その引数をプロシージャとして、そのプロシージャの引数には数字の100を束縛する」。したがってcall/ccから見ると、「call/ccを呼んだときの継続に数字の100を適用する」。トップレベルで(E)を呼べば、そのときの継続は「評価して返す」というREPLの基本動作だけなので、100が返る。
gosh> (call/cc apply100)
100


ところで「引数を1つとるプロシージャ」は、わざわざapply100を使わなくても身近にある。call/ccだ。ということは、次の式もきちんと評価される。
(call/cc call/cc)    ; (F)
call/ccは、「引数を1つとって、その引数をプロシージャとして、そのプロシージャの引数にはそのときの継続を束縛する」。したがって左のcall/ccから見ると、「左のcall/ccを呼んだときの継続に右のcall/ccを呼んだときの継続を適用する」。トップレベルで(F)を呼べば、そのときの継続は「評価して返す」というREPLの基本動作だけなので、右のcall/ccを呼んだときの継続が返る?
gosh> (call/cc call/cc)
#<subr continuation>


右のcall/ccを呼んだときの継続って何?(結局疑問形で時間切れ……)

2007/03/05

先月、連番を作るのにintegというプロシージャを定義して喜んでいたら、素直な方法はこれだという指摘をいただいた。ありがとうございます。
(define integ
(let ((n 0))
(lambda ()
(set! n (+ n 1))
n)))
なるほど。これはきっともう一度"Seasoned Schemer"を読み直すべきだな。

ところで、こういうクロージャをSICPの第3章の評価モデルで表すとどうなるんだろう。
まず考えやすいようにletをlambdaに変換して、
(define integ
((lambda (n)
(lambda ()
(set! n (+ n 1))
n))
0))
nが0に束縛された環境以下でset!が機能するのだから、こんな感じでいいのだろうか?

integ-1
『On Lisp』を読んでいると、これはLisperのためだけの本にするのはもったいないなあと強く感じる。
LisperじゃないとLispのコードが読めないので結局Lisperにしか読めないという制限はあるけど、Paul Grahamが書いていることの基底にはもっと普遍的な内容がある。
だから、『ハッカーと画家』としてまとめられたようなエッセイを通して、非Lisperであっても彼の思想に触れられるのはありがたいことだと思う。

とはいえ、『On Lisp』に書かれている表面的なこと、つまり「マクロ」がどうでもいいかというと、そんなことはまったくない。
「マクロがあるLispは最強」を超訳して対偶をとると「人手で繰り返すような作業を効率化できないシステムは屑」になる。
本当?
Paul Grahamは、マクロという仕組みが特筆すべきものであり、それがLispという道具を最強にしていると言っていると思うので、どんな道具であれ自分の使っている道具に適切なオレマクロレイヤを組み入れればそれなりに強力にできるよね、という意味で本当。

ここ1~2年くらいの仕事では、原稿の文章構造(XMLだったり簡易的なタグがつけられた平文だったり)をLaTeXに変換して印刷所に渡すPDFを生成するようにしている。
つまり、旧来のようなMacintosh上でのDTPソフトを使った組版作業を捨てている。Macintosh上でのDTPソフトを使った組版作業の何が悲しいかっていうと、だいたい技術書の原稿なんておなじような構造を持っているのに、それを毎度毎度人間が手作業でDTPソフト上に「絵」としてレイアウトしなきゃいけないとこ。ここで「毎度毎度」というのは、別の新しい本を作るたびだけじゃない。1冊の本の制作においてさえ、原稿に修正が入るたびに「絵」を描きなおす作業を繰り返さなければいけない。

そんな三途の川で石を積み上げるような地獄から抜け出るのに必要なのは、まさにマクロ。TeXのマクロがその地獄から解放してくれる……ただし編集者を別な地獄に陥れるという方法で。前の地獄が虚しさに起因するとしたら、今度の地獄はマグマ溜まりに架かったつり橋を歩かされるみたいなものだ。いつ足を滑らせて丸焦げになるかわからない。

おなじ歩くなら石橋を渡りたいので、TeXしかなかったらTeXのマクロで処理せざるを得ない処理の大部分を、TeXからは切り離してGaucheで実現しているのが現在の制作方法だといっていい。つまりマクロの層をGaucheで提供するってこと。原稿の文章構造をGaucheでLaTeXのコードに展開し、それをpLaTeXで処理するわけだ。これだけでずいぶん歩きやすくなる。LaTeXの実行時にしか知りえない情報(ページの幅とかテキストの大きさとか)にかかわる部分は本質的には扱えないけど、それも汎用を目指さなければ抜け道がないわけではない(文字数や文字の大きさを書籍ごとに決めうちすることで擬似的に代用できる)。

『On Lisp』を読んでいてびっくりしたのは、この方法が見た目にも『On Lisp』のマクロに近いということ。マクロの言語(Gauche)とコンパイルの言語(LaTeX)が異なるという大きな違いはあるけど、Gaucheが採用している文法のおかげで、ただのテキスト処理のコードなのに見た目がCLのマクロっぽくなる。たとえばTeXの環境を定義するにはこんなコードを使っている。
(define (make-tex-env env-name opt-arg args)
(let ((arg-list (string-join (map x->string args) "}{")))
(define-tag-process
env-name
(lambda (parent)
(if (not (should-not-linebreak? parent))
(display "\n"))
(display #`"\\begin{,env-name}")
(if (not (equal? opt-arg ""))
(display #`"[,opt-arg]"))
(if (not (null? args))
(display #`"{,arg-list}"))
(display "%\n"))
(lambda (string parent) (display-without-white (kick-comment string)))
(lambda (parent) (display #`"\\end{,env-name}%\n")))))
テキストリテラルのあたりに見える「,」や「`」がCLのマクロっぽさをかもし出しているように感じるのは僕だけ? これらはGaucheでテキスト処理に採用している文法だと理解しているんだけど、それがいかにセンスのいい選択であるのか、On Lispを読んで初めて気付いた。すごいよGauche。(ちなみにdefine-tag-processは入れ子になった文章構造を再帰的にTeXに変換するコードに渡すためのクロージャとして定義したもの。)

ところで、こういう「スクリプトで原稿をLaTeXに変換→PDFにコンパイル」という制作方法について、去年のはじめくらいまでは子供だましみたいだなあと卑下していた。
それは殊更に困難もなく目指すものが実現できてしまっていたからなわけだけど、困難なく実現できたのは、こういう優れたセンスのGaucheという処理系や周囲の諦観交じりの援助(とくにCさん)があったからに過ぎないわけで、感謝すると同時に、じゃあ僕にはその返答として何ができるんだろう? 本当はここでTeX界隈にも深く感謝すべきだと理解はしているけど、結局ぶーぶー言いながらTeXを使わざるをえない地獄にあえいでいる現状には満足すべきでないと思うので、自戒をこめてスルー。

なんだか『On Lisp』とは関係ない話になってきた。とにかく日本語版の『On Lisp』は、いま主編集者のhisashimさんが最後の追い上げをかけているので、早ければ3/24の週末には大きな書店で入手できるはずです。

2007/03/03

先月だかその前だかにBloggerをβから正規版に乗り換えたところ、コメントのお知らせメールの転送が止ってしまって、いただいた希少なコメントに気付くのが遅くなってしまった。すみません。そのうえ、これまでつかっていたatom-blogger.elが使えなくなっちゃったので、更新するのがやたらに面倒。結局2月は1件しか書かなかったのか。

反省して、まずは新しいBloggerのEmacsクライアント(g-clientというらしい)を導入する。

An Emacs Client For Blogger
http://buzz.blogger.com/2007/03/emacs-client-for-blogger.html

上記ページの手順で英語の記事はうまくいくんだけど、日本語の記事はうまくアップできないようだ(つまり、このエントリはg-clientではアップしていない)。

あと備忘録。そのままでは毎回atomのURLを入力しなければならず使いにくいので、将来インストールしなおすときはmakeの前にgblogger.elを編集するのを忘れないこと。
(defun gblogger-new-entry (url)
"Create a new Blog post."
(interactive
(list
(let ((url (read-from-minibuffer "Post URL:")))
(cond ((string= url "")
"http://k16ex\.blogspot\.com/feeds/posts/default")
((string= url "note")
"http://k16journal\.blogspot\.com/feeds/posts/default")))))
(declare (special gblogger-auth-handle gblogger-new-entry-template
gblogger-generator-name gblogger-publish-action))
(g-auth-ensure-token gblogger-auth-handle)
(let* ((title (read-string "Title: "))
(buffer (get-buffer-create (if (string= title "") "temp" title))))
(save-excursion
(set-buffer buffer)
(erase-buffer)
(gblogger-mode)
(setq gblogger-this-url url)
(goto-char (point-max))
(insert
(format gblogger-new-entry-template
gblogger-generator-name gblogger-generator-name
gblogger-author title)))
(switch-to-buffer buffer)
(setq gblogger-publish-action 'gblogger-post-entry)
(search-backward "<div" nil t)
(forward-line 1)
(message
(substitute-command-keys "Use \\[gblogger-publish] to publish your edits ."))))

2007/02/28

答えはわかってるっていう問題は少なくない(実際、答えは42だ)。
問いをはっきりさせるのに抽象化が必要なんだと思う。

2007/02/17

連番を作りたい。ようするに、こんな動作をするプロシージャintegがほしい。
gosh> (list (integ) (integ) (integ))
(1 2 3)
まあ、グローバル変数を破壊的に更新すればいい。
(define n 0)
(define (integ)
(set! n (+ n 1))
n)
でもそんなSchemeコードはいやだ。主に気分的な理由で。こういう問題にはcall/ccを使うのがオレブーム。
(define (integ)
(let R ((n 0))
(call/cc
(lambda (k)
(set! integ (lambda () (R (+ n 1))))
(+ n 1)))))
しかしこれではcall/ccの意味がまったくありませんでした。すみません。以下で十分です。
(define (integ)
(let R ((n 0))
(call/cc
(lambda (k)
(set! integ (lambda () (R (+ n 1))))
(+ n 1)))))

気を取り直して。応用。
(let R ((str "hello hello hello hello"))
(rxmatch-if (rxmatch #/ / str)
(space)
(R (regexp-replace space str #`",(integ)"))
str))

=> hello1hello2hello3hello"
Gaucheに用意されているregexp-replace-allという便利な関数と組み合わせると編集者にとってはプチよろこばしい。
gosh> (regexp-replace-all #/ / "hello hello hello hello" (lambda (m) (integ)))
"hello4hello5hello6hello"

ところでintegを次のように定義するとうまくいかない理由を昨日から考えているんだけど、わからない。これでもとくに問題なさそうなんだけど、(list (integ) (integ)) のように実行しても1つめの(integ)が評価されるだけ。
(define (integ)
(call/cc
(lambda (skip)
(let R ((n 0))
(call/cc
(lambda (k)
(set! integ (lambda () (k '())))
(skip (+ n 1))))
(R (+ n 1))))))

2007/01/20


おれカネゴンさんの一言をきっかけに、ひさしぶりにコンサートに行ってきた。去年はひとつも行かなかったなあ。

東京都交響楽団 第638回定期演奏会 Aシリーズ
http://www.tmso.or.jp/j/concert_ticket/detail/index.php?id=3024

こちらで絶賛されている松村禎三の「ピアノ協奏曲 第1番」は聴いたことがなかったけど、生で野平一郎の協奏曲ソロが聴けるということで、すぐにチケットを手配。オネゲルの5番が「生で」聴けるというのも即決したポイント。ミヨーも聴いたことないけど、まあ、ミヨーのオーケストラ曲は僕にとってどれも似たような印象だからハズレはありえないだろう(これは賛辞です)。幸い(主催者側にとっては残念ながら)、席は豊富に残っていた。もう1週間前なのに。野平なのに。

当日、会場は予想どおり空席が目立つ。ぼくは3階だったので、開宴前に上からぼうっと下を見ていると、おじいさんが車椅子で会場に。よくみると松村禎三本人で、やるせなさがこみあげる。この演目だから客が入らないというより、たぶんぼくのような潜在的な客を逃しているのが大きいんだろう。実際、ぼくも公演の存在すら知らなかった。

演奏については、まず、この公演に足をはこぶきっかけを与えてくれた「新しい世紀のための音楽」のレビューを。

ぼくには、松村禎三の2曲はどちらも文句のつけどころがなかった。とくに「管弦楽のための前奏曲」では、CDで聴いて知っていた以上にピッコロ6本がからみあう迫力がすごくて鳥肌もの。「ピアノ協奏曲第1番」は、もう、やっぱり野平さんすげー。苦労している様子は少なくとも僕には感じられなかった。はじめて聴いた曲だけど、竹薮がざわざわしているようなピアノソロの冒頭になつかしい印象を受けるのは松村禎三本人がいっているところの「アジア的な群」というやつにアジアで生まれ育った人間として共感するのでしょうか。よくわかんないけど。とにかく、それからピアノが次第に高揚していって、気づいたらオーケストラがうねりながらからんでいる。自然科学的には風がふいて竹薮がざわめくはずなのに、竹薮がざわめくことで空気をゆらし風を巻き上げているみたいな。そういううねりが2回くらい繰り返され、冒頭のようなピアノソロのざわざわで静かに終わる。この協奏曲は、ほんとうにすごいや。その世界感をきちんと提示してくれた都饗と指揮の下野さん、野平さんの演奏ではじめて曲を聴くとができてよかった。この協奏曲はストラビンスキーとバルトークと前期ケ−ジの好きな全世界の人に心からおすすめ。

後半はミヨーとオネゲル。ミヨーは誰がやっても同じようにハッピーになると思うのでいいとして(これは賛辞です)、オネゲルの5番は最後になって管がちょっとばて気味に感じられた。第1楽章はとてもよかったけど、それも中盤で弦による主題を背景に木管がタリラータリラーってするとことか、だいぶ弦に潰されてしまっていた感じ。第3楽章のラストになると、みんなちょっぴりぐだぐだ。でもとてもよい演奏会だった。C席3500円でこれだけ楽しめるとは。こんな構成はあまりないだろうけど、都饗の定期演奏会はこれからもチェックするようにしよう。

2007/01/18

常識なのかもしれないけど……

LaTeXのリストの体裁を制御する変数(『The LaTeXコンパニオン』の72ページに書いてあるやつ)のうち、リスト全体の上下のアキを制御する変数がなぜか3種類ある。
  • \topsep
  • \parskip
  • \partopsep

この違いが本を読んでもよくわからない。本の解説によると、\topsepは「最初の項目と続く段落との空き」で、\partopsepは「環境が新しい段落を始める際に、\topsepに追加される余分な空き」で、\parskipにいたっては説明すらない。

経験的に知っていること。

\topsep
本文とリストの上下の余白の高さ。つまり、リストが入れ子になっているような場合には、\topsepの値がどうであろうと内側のリストの上下には余白ができない。

\parskip
リストの上側には、\parskipで指定したぶんだけ余分に空きができるようだ。下側には空きができない。入れ子のリストの内側でも同じ挙動。

\partopsep
リストの上下の余白の高さ。入れ子のリストの内側でも同じように余白ができる。


間違っていたら訂正したいので教えてください。

2007/01/14

ついに歯ぐきに穴をあけてインプラントの支柱を埋め込んだ。抜歯は来週。しばらくまともなものが食べられないうえに感染症予防のための抗生剤でぼうっとする。でも明日は出社か。はたらきたくないよう。

奥様は仕事だし自転車にのる気力もない日曜なので、有限状態機械ごっこをしてひまをつぶす。とはいえ、遷移図を睨んでいても頭が弱くてちっとも理解がすすまないので、有限状態機械の動作を模倣する評価機をSICPの第4章を参考にしてつくってみよう。ザ 本末転倒。
(define (q0 input)
(cond ((= input 0)
(output 0)
(transit! q0))
((= input 1)
(output 0)
(transit! q1))))
(define (q1 input)
(cond ((= input 0)
(output 1)
(transit! q0))
((= input 1)
(output 1)
(transit! q1))))

(define (output b)
(display #`"Output: ,b"))
(define (eval-in-current-state input state)
(state input))
(define current-state q0)
(define (transit! q) (set! current-state q))

(define (fsm-loop)
(newline)
(let ((input (read)))
(let* ((state current-state)
(output (eval-in-current-state input state)))))
(fsm-loop))
q0とq1は、いわゆる遅延機械。
gosh> 
(fsm-loop)



1

Output: 0

1

Output: 1

1

Output: 1

1

Output: 1

1

Output: 1

0

Output: 1

0

Output: 0

0

Output: 0

0

Output: 0

1

Output: 0

1

Output: 1

0

Output: 1

1

Output: 0
……

2007/01/06


奥様が「お行」のピアノ曲を聞きたがっていた。「お行」ってなんだ、と聞くと、ロロロロロ……とかポポポポポ……とかで擬音化できるような曲だという。一般にピアノの音の擬音化は「ポロロン」みたいな感じなので、だいたいどの曲も「お行」なんじゃないのか? しかし、どうやら違うらしい。僕がいつも聞いているピアノ曲は、彼女にとってキョキョキョキョキョ……とかリリリリリ……なんだと。つまり「い行」か。

とりあえず彼女がイメージ先行で選んできた「お行」のCDはショパン。なんだ、そういうのでいいのか。つまり、比較的耳にやさしい音がほしいってことね。そこでブレンデル先生のモーツァルトのソナタを渡したら、案の定、求めていたものに合致したようだ。彼女の脳でキョとかリに聞こえるのは、きっとナンカロウとかメシアンなんだろう。たしかに鋭い音が跳ねっ返っているような雰囲気が「い行」に聞こえてきたよ。面白いのはドビュッシーなんかも「い行」系列にくくられていること。方法論は独自だけど結果的に納得できるカテゴライズがなされていることに驚く。その後の実験的な推測により、僕が聞きたい音と彼女が求めている音のぎりぎりの交わりは、シューベルトの後期ソナタ付近にありそうだということがわかった。

残念ながらうちには「お行」系列のまっとうなCDがたいへん少ないので、すこし補充するかなあ ← 結局CDが買いたい。