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 ."))))