Gambitでクロージャをシリアライズ

できるよという話。

Gambitに強力なシリアライズ機能が付いていることをyieldした途中の状態をファイルに保存とかって出来ますか - 崩壊現実-全てはvirtualに収束する-を見ていてふと思い出したので、クロージャを使って同じようなことを試してみた。

(define dump-file "counter.out")

(define (make-counter)
  (let ([n 0])
    (lambda ()
      (let ([ret n])
        (set! n (+ n 1))
        ret))))

(define counter
  (if (file-exists? dump-file)
    (call-with-input-file dump-file
      (lambda (p)
        (u8vector->object (read p))))
    (make-counter)))

(println (counter))
(call-with-output-file dump-file
  (lambda (p)
    (write (object->u8vector counter) p)))

それで、こんな感じに実行できる。

$ gsi yield_serial.scm
0
$ gsi yield_serial.scm
1
$ gsi yield_serial.scm
2
$  


どうやってクロージャをシリアライズしてるのか詳しくは知らないけど、クロージャの自由変数に束縛されてる値もろともシリアライズしているということなのかな。実際、

> (define f (make-counter))
> (f)
0
> (f)
1
> (define g (u8vector->object (object->u8vector f)))
> (g)
2
> (g)
3
> (f)
2
> (f)
3
> 

という結果が得られるので、自由変数の値がコピーされてるのは確かで、別にコピー元のクロージャとまったく同じローカル変数を参照しているわけじゃないみたい。当たり前といえば当たり前か。

ただし、ポートだとかスレッドだとかいったオブジェクトはシリアライズできないらしく、クロージャがこれらのオブジェクトを捕捉している場合には、クロージャもシリアライズできない様子。


で、実はGambitは継続もシリアライズできるという話で、当初はそれをネタにしようと思ったのだけど、継続はクロージャよりもいろんなものを捕捉してしまう可能性があって、それを避けるのにいくらか手順を踏まなきゃいけなくて、サクッと理解できる例になりそうになかったので諦めた。興味のある方はこちらを参考にどうぞ。

余談

日本では、Schemeの処理系はGaucheがズバ抜けて人気で、PLTがチラホラ、他の処理系がごく稀に使われてるっていう構図(のように思えるん)だけど、おもしろ機能をもったいろんな処理系がもっと流行れば楽しいんじゃないかなぁと思う。