Let's start Scheme

2012-11-30

続 マクロ戦争再び

すこぶる体調が悪いが頭はそれなりに稼動するので案をベッドの上で考えていたりした。

現状の問題としては、同一の環境でリネームされた識別子(本来はシンボル)が同名にならないというものだ。これは本質的に2つの問題を含んでいることを示唆している。
  1. 識別子を用いてシンボルの同一性を取ろうとしていること
    1. α変換が不完全である
  2. 識別子が本来の意味を越えて使用されているため、複雑なものが複雑怪奇にグレードアップしている
    1. 識別子=構文オブジェクト=パターン変数=リネームされたシンボルになっている
では、どうすればいいかということを考えていた。とりあえず1と2の問題の一部を解決するアイデアが以下である。
  • 使用されている環境に応じて適切にシンボルのリネームを行う
王道である。実際これくらいしか思いつかなかったともいう。問題になるのは、どの環境をもって同一の環境というかという点である。Sagittariusではマクロ展開時に3つの環境を参照することができる。
  1. コンパイル時の環境
  2. マクロ束縛時の環境
  3. マクロ展開時の環境
自分の中でもなんでこんなに環境が必要なのか実は整理できていないのが問題なのだが、それぞれ全て違う。細かいこと(細かくないが)はとりあえず置いておいて、とりあえず以下のプログラムを考える;
(import (rnrs))
(let ((bv #t)) ;; ※1
  (define-syntax foo
    (lambda (x)
      (syntax-case x ()
        ((_)
         (with-syntax ((set (let ((type #'bytevector-u8-set!))
                              #`(#,type bv 0 1))))
           #'(let ((bv (make-bytevector 1)))
               set
               bv))))))
  
  (let ((bv #f))
    (display (foo))))
まぁ、前回とほぼ同じコードである。違いは、マクロ束縛時環境が何かを捉えている点である。ここで問題になるのは、全てのbvが同一のシンボルになる必要がある点である。そうすると、コンパイル時環境を使うのはまずいことになる。letで束縛されたtypeが入ってくるからだ。そうすると、二つ目のbvと環境が異なるため、生成されるシンボルが異なる。
この状態では、マクロ束縛時と展開時の環境が多少違う。このパターンを考えるとマクロ束縛時の環境を使ってリネームするのが正しい気がする。ただし、リネームされたシンボルは※1と同名になる必要がある。これは、with-syntax>で生成されたものがそれを参照している必要があるからだ。また、最終的に返される構文で束縛されるbvも同様である。それによってシャドウイングされて結果正しい値を返す。
もし、このスクリプトでマクロ展開時の環境を使うと、(まぁ、結果的には問題ないのだが)、二つ目のbvにリネームされることになるのでうれしくない。

なんとなくどうすればいいのか見えてきた。結果として以下のように修正すれば、いいのではないだろうか?
  1. コンパイラ側のリネームを(uninterned)シンボルを使うように修正
  2. マクロ展開器ではマクロ束縛時の環境を元にシンボルを(uninterned)シンボルにリネーム
方針は固まったので、後は実装か・・・

2012-11-27

マクロ戦争再び

正直何度目だ、戦争が勃発するのは・・・

ほぼピュアR6RSで書かれたライブラリでindustriaというものがある。驚嘆するほどでかい上に、Sagittariusのお株の一つである暗号処理がサポートされている。(別にライブラリの宣伝をしたいわけではない)

このライブラリ内で使われているマクロが動かないというバグレポートを受けた。最初はmake-variable-transformerが参照している環境が違うだけかと思ったのだが、実は裏にもう一つ潜んでいた。こっちは非常に厄介な、しかもずっと悩んでいる問題の一つ。端的に問題を顕在化させるコードが以下;
(import (rnrs))
(define-syntax foo
  (lambda (x)
    (syntax-case x ()
      ((_)
       (with-syntax ((set #'(bytevector-u8-ref bv 0)))
  #'(let ((bv (make-bytevector 1)))
      set
      bv))))))

(foo)
with-syntaxで作られた新しいパターン変数(ということにしている)内にあるbvと最終的に返される構文内で使われているbvが同じでなければならないという問題。

これはSagittariusではうまく動かない。なぜかという原因も分かっていて、最初のbvと2回目のbvは使用される環境(正確には違うけど便宜上こう呼ぶ) が違うから。これと類似の問題に、R6RSテストスイーツのbreakがあるが、あれはwith-syntax上ですでにパターン変数として作られているので、なんとか識別する。

これの本質的な問題は、syntax構文が同一環境で呼ばれても同一の識別子を返さないことにある。本来ならば、最初と2回目は同一の環境なので、どちらのbvも同じ名前にリネームされるから最終的な形としては問題なく動くようになる、はずなのだけど、そうは問屋がおろさないので困っている。

解決策としては2つあると思っていて、一つは王道、識別子にリネームされた際の環境、フェーズその他もろもろの情報を付加してやり、identifier=?はそれらをまじめに比較するようにする。二つ目は邪道(というか、スパゲッティ生成法)、マクロ作成時に作られる環境にフィールドを一つ追加して、何が何にリネームされたかということ保持しておき、それを環境が拡張されるても共有する。

正直、どちらの方法もかなり大掛かりな変更が必要になる。やりたくないが、バグとして報告された以上なんとかしないわけにもいくまい・・・

ってか、これってR6RSで動くって保障されてるのかな?されてるよなぁ・・・

2012-11-22

キャッシュを考える

ふとQiitaのLisp Reader Macro Advent Calendar 2012のネタを考えているときに思ったこと。

Sagittariusはパフォーマンス(ほぼレスポンスだが)向上のためにライブラリ単位でFASLのようなものを持っている (正確にはライブラリファイル単位だが)。普通にスクリプトを書く分には気にする必要はないのだが、リーダーマクロが絡んでくると多少考える必要が出てくる。たとえば、組み込みの正規表現はリーダーマクロを用いて読み取った場合リテラルオブジェクトとして扱われる。これはその方がパフォーマンスがいいからに他ならない。

ここで、ユーザー定義のオブジェクトを考える(Qiitaに書く予定のネタは文字セットなんだけど)。たとえばCLOSを用いてオブジェクトを作成するが、リーダーマクロでリテラルとしても返せるようにしたとする。そうすると何が起きるか?普通にスクリプトとして使用されるだけなら特に問題はないのだが、ライブラリ内で使用すると話が違ってくる。

現状ではキャッシュ機構はユーザー定義のオブジェクトをキャッシュする方法を暗に知る方法がない。SRFI-4ではユーザー定義のリテラルを返すようにしているが、これは明示的にどうそのオブジェクトをキャッシュするかということをScheme側で制御しているからできるのである。しかし、いちいちそれを書くのが面倒な場合の方が大体多い。

なんとかならないかなぁと思っていたのだが、なんとなく「完全Scheme」で定義されたオブジェクトなら一般的な方法でいけるような気がしてきている。それは、CLOSのオブジェクトは基本スロットの中身さえ保存してしまえば何とでもなるからだ。スロット名と値を保存して、復元時は単にslot-set!でセットしてやる。

ここで、問題になるのは、「キャッシュできない組み込みオブジェクト」だろう。現状でsubrをうまいことキャッシュする方法を思いついていない。また、自由変数を含むクロージャーがスロットに入っていた場合とかも困る。まぁ、あくまでベストエフォートだと言えばいいので、こういったキャッシュ不可能なものが来たら弾いてしまえばいいといえばそうなんだけど。

不安材料の方が多い気もするし、どうしたものか・・・?

2012-11-18

Hoge VeluweとKröller Möller美術館

Meetupのイベントで行ってきた。Utrecht以東に行くのは実はこれがはじめて。Adventureはたどり着く前から始まっているのである。(MeetupのグループがAdventureを冠している)

ま、何はともあれ写真。僕の気力が続く限り撮った。加工するのが面倒だったからファイルが巨大だw
Utrecht Central。いつきても代わり映えはしない。そしてトラブルが多いというイメージの駅。(実際帰りはLeidenへの直通がなかった・・・orz)

ハイキング開始。きれいな丘だろ?これでもオランダなんだぜ。

もう少し傾斜があれば軽く山登りな気分。

オランダとは思えない風景の一つ。

森の中を進む。

森が終わると今度はサバンナw

あたり一面まっ平ら。ま、やっぱりオランダだよね。

微妙にこの風景はSaskatchewanとかManitobaを思い出す。何もない平らな風景。

3時間に及ぶハイキングの後、小さなレストランに到着。あの小屋みたいなのがレストラン。

ここからはKröller Möller美術館の中。これは正確にはその庭。

途中で見つけた何かしら。これの裏に、三段腹をモチーフにした何かがあったけど、写真取り忘れたw

木漏れ日が差してる絵って好きなんだよね。 ま、iPhoneのカメラではこの程度が限界だけど。

芸術家の考えることは一般人には理解できないけど、これはひときわ、なんぞこれw?だった。

改装中の部屋にあった絵。

本物(だと思う)のゴッホの絵。この美術館はゴッホのコレクションがオランダで2番目に多いらしい。一番はアムステルダムのゴッホ美術館だと思う。

ありがたや~w

自分が写りまくりw合わせ鏡があって、どれくらい光が届くんだろうと気になって撮った。

夜の帳が下りようとしている中、正門まで2kmのウォーキング。10分後にはえらい暗くなっていた。

帰り道はUtrechtからLeidenへの電車がなかったので、Den Haag経由で帰る必要があったがまぁ無事に帰宅。3時間+30分のウォーキングで足が痛いw
正直、オランダにこんなところがあるんだ、と感動した一日だった。そして、山っぽいところを歩いているときに、あぁ、やっぱり自分は山の方が好きなんだなぁと実感した。

2012-11-16

writeがスタックを食いつぶす

Gaucheのソースツリーを眺めていて、writeがCのスタックを食いつぶすのを直したというようなコミットを見つけた。同じことが起きるよなぁと思いつつ、とりあえずテスト。以下は使用したコード(R7RS形式なのは最後にチェックしたのがChibiだから。当然Sagittariusでも動く)
(import (scheme base) (scheme write))
(define (call-with-string-output-port proc)
  (call-with-port (open-output-string)
    (lambda (p) 
      (proc p)
      (get-output-string p))))

(display
 (let loop ((cnt 0) (ls '()))
   (if (< cnt 1000000)
       (loop (+ cnt 1) (list ls))
       (string-length (call-with-string-output-port
                       (lambda (p) (display ls p))))))) 
(newline)
とりあえず、どのくらいの処理系がこれをSEGVを起こさず処理するのかチェック。以下は結果。
200002を正しく返した処理系
Chez Scheme, Racket (plt-r6rs), Larceny (R6RS)
SEGVもしくは処理が終わらなかった処理系
Sagittarius, Chibi, Mosh, Ypsilon
動く処理系で、ソースを確認できるのはRacketとLarcenyだけなので、とりあえず確認してみた。

Larcenyはなぜ動いているのか正直分からなかった。コードがSchemeで書かれているので、ひょっとしたら最適化でうまいこと動いているのか、Cで書いていない分スタックの消費が少なくなっているのかは不明。

Racketはスタックの底にたどり着いたらlongjmpするという荒業だった。たしかに、ありだよね。

Gaucheは0.9.4は再帰呼び出しをせず、gotoで済ますという方法。ただ、そのためにメモリを結構使用するんじゃないかなぁと思ったり。

Sagittariusは(ユニットテストの)メモリの使用量が半端ないので、可能な限り省エネで行きたいところ。なので、やるならRacket方式か、気合でなにか良い案思いつく必要がある。longjmpのオーバーヘッドってどれくらいなんだろう?

Sagittarius 0.3.8 リリース

Sagittarius Scheme 0.3.8がリリースされました。今回のリリースからR7RSドラフト7がサポートされます。ダウンロード

修正された不具合
  • ローカルマクロをマクロ内で定義した際にSEGVが起きる不具合が修正されました。
  • (dbd odbc)を用いて存在しないレコードを更新しようとした際にエラーが投げられる不具合が修正されました。
  • get-bytevector-allがソケットポートからデータを全て読み込まない不具合が修正されました。
  • binary-port?及びtextual-port?がカスタムポートに対して適切に動作しない不具合が修正されました。
  • #n=をリーダが適切に読み込まない不具合が修正されました。
  • remainderの第一引数に浮動小数点数を与えるとSEGVが起きる不具合が修正されました。
  • (sqrt -1)が非正確数を返す不具合が修正されました。
  • (zero? 0.0+0.0i)が#fを返す不具合が修正されました。
  • -1iのような形式の複素数が正しくない符号を返す不具合が修正されました。
  • ライブラリのバージョンに0が使えない不具合が修正されました。
  • ライブラリバージョンリファレンスの形式がエラーを投げる不具合が修正されました。
  • appendが引数1つで呼び出された場合に()を返す不具合が修正されました。
  • 全ての未束縛exportが束縛された値を持つよう修正されました。
改善点
  • (cond ((assq a b) => cdr))の形式をコンパイラが可能であれば組込みインストラクションを使うように改善されました。
  • ビルドプロセスが改善されました。
    • 多くの依存ライブラリがFIND_PACKAGEを用いて検索されるようになりました。
    • libffiが見つからなかった際にLinuxでもバンドルされたlibffiが使用可能になりました。(Ubuntu 32 bitでのみテストされています)
  • ODBCライブラリのblob型ポートが作成時に全てのデータを読み取らないように変更されました。
新たに追加された機能
  • リーダーマクロの有効範囲がファイル単位からポート単位に変更になりました。
  • R7RSドラフト7がサポートされました。
  • glob手続きが追加されました。
  • sashの-Lオプションが-L'*'形式をサポートするようになりました。これはglobで見つかった全てのディレクトリをロードパスに追加します。
新たに追加されたライブラリ
  • SRFI-105が追加されました。#!read-macro=curly-infixで有効になります。#!curly-infixではないことに注意してください。
非互換な変更
  • R7RSのライブラリが大幅に変更になりました。これらはドラフト5からの変更で、後方互換性はありません。

2012-11-15

ブレインストーミング

考えをまとめるのに紙に書くのが面倒なのと、記録を残しておいた方がいい類のものなのでメモ。

以下のコードを考える。
(read (open-string-input-port "#!fold-case ABC"))
(read (open-string-input-port "ABC")) ;; こいつは何を返すか
恐らく書いている方の期待する結果はABCというシンボルだろう。だが、これは予想に反してabcを返す。(Sagittariusでは#!fold-caseは全部小文字にして返す) 。こんなの書くやついないよ!と思っていたのだが、意外なことに自分がこれではまったのでやはり問題かなと思うようになってきた。

何が問題か?

現状ではSagittariusはファイル単位でリードテーブルの切り替えを行う。っが、上記のようなものだと同一ファイル上でreadが走るのでファイル単位で行うと残念な結果になる。つまり、ファイルではなくポート単位で行う必要があるということになる。

どう直すか?

ポート単位にするというのは結構大掛かりな変更になる。現状の実装では、VMが現在のリードテーブルを保持して、loadが走る際にポートに保存、loadが終わったら保存したテーブルをVMに戻すという割と回りくどいことをしている。
これをポート単位にするにはどうすればいいのか?とりあえず簡単に思いつくのはVMが現在loadしているポートを保存するというもの。loadは必ずファイル単位で行われるので、現在のloadingポートが分かれば何とかなりそうである。となると問題になってくるのは、どの段階でポートにリードテーブルを持たせるかというもの。可能性としてあるのは、
  1. ポート作成時。(全てのポートはリードテーブルを持つ)
  2. リードテーブルを変更するようなものを検出したとき。(オンデマンド)
上記のようなコードだと、2つ目のポートに持たせるのは無駄以外のなんでもないような気がするので、やるならオンデマンドだろうか。

方針は決まったので実装するか。

2012-11-14

この発想はなかった

Chibi-SchemeのIssue 149にあった面白い発想のdefineをsyntax-caseで実装してみた。
(import (rnrs))
#|
;; ※1
;; 投稿した2分後にsyntax-case一回だけでいけることに気づいた。
;; なので、コード直下のぼやきはこのコードに対してのもの。
(define-syntax define-function
  (lambda (x)
    (define (compose expr)
      (let loop ((expr expr))
        (syntax-case expr ()
          (((name . formals) body ...)
           (identifier? #'name)
           #'(define name (lambda formals body ...)))
          (((name&formals . rest) body ...)
           (not (identifier? #'name&formals))
           (loop #'(name&formals (lambda rest body ...)))))))
    (syntax-case x ()
      ((_ exprs ...)
       (compose #'(exprs ...))))))
|#
;; すっきしたバージョン。これなら処理系関係ない。
(define-syntax define-function
  (lambda (x)
    (syntax-case x ()
      ((k (name . formals) body ...)
       (identifier? #'name)
       #'(define name (lambda formals body ...)))
      ((k (name&formals . rest) body ...)
       ;;(not (identifier? #'name&formals)) ;; この行要らない
       #'(define-function name&formals (lambda rest body ...))))))

(define-function ((f x) y) (+ x y))

(print ((f 1) 2))
多少以上に余計な手間をかけているのだが、マクロ周りがR6RS準拠な処理系と違ってSagittariusでは識別子のリネームに制限がある。それは、同一のsyntax構文内でリネームを行わないと識別子が同じにならず、unbound variableエラーを投げるというもの。なので、上記は本来ならquasisyntaxを使ってもう少し分かりやすく書けるのだが(まぁ、どちらが分かりやすいかは人によるが)、Sagittariusで動かすために式を全部一緒に処理してやる必要がある。(※1のコード)

この制限はずっと気づいているし、問題だと思っているのだが、いかんせんスマートな解決方法が思い浮かばないのと、慣れてしまえば別に気にならないので放置してある。0.4.0辺りまでに直したいという思いはあるが、あるだけともいえる・・・(マクロ周りは本当に鬼やぜ)

さて、ようやく本題。上記のマクロがどのように展開されるかというのが個人的には面白いなぁと思ったという話。Chibiのissue見れば答えは載っているのだが、引数の個数にも寄るが、この構文簡易カリー化と取れなくもない。上記の手続きfは以下のように展開される。
(define f (lambda (x) (lambda (y) (+ x y))))
たとえば、SXPathはこんな感じの手続きをころころ作るのだが、実装を見てみると(当然なんだけど)明示的にlambdaをいっぱい書いている。でも、これ使えばそのわずらわしさから開放される!というわけだ。

僕自身、あまりそんな使い方しないんだけど、マクロ使えばこんなこともできちゃう!というちょっとした例。

2012-11-11

R7RS対応

ドラフト7の範囲の実装が大体終わった。Chibi SchemeのR7RSテストケースもほぼ通るようになっている。(通らないのは、R6RSと競合する部分とFlonumの精度が決め打ちになっている部分と、R7RS上では不明瞭に見えてかつ僕がChibiの挙動の方がおかしいと思う部分)

他の人が仕様書に対して書いたテストというのは意外と自分ではチェックしない部分の挙動があったりしてバグ潰しに役立つということが分かった。実際、Chibiのテストケースを通るようにした際に6個くらいバグをつぶした。中にはSEGVを起こすようなのもあって、普段使わない、書かない系のコードというのの中にこそバグが含まれているというのがよく分かった。

現状でR7RS準拠度はevalが環境を破壊的に変更するのと(問題だとすら思っていない)、R6RSなのでbinary、textualポートの区切りがはっきりしている点を除けば全て準拠していると思う。Chibiですら対応していないinclude-library-declarationsも入っているw

今週末にリリース予定の0.3.8からR7RSドラフト7に対応しているという宣伝。

未束縛エクスポートのチェック

R6RS及びR7RSではexport句に未束縛なシンボルを書くとエラーになるとある。しかし、Sagittariusでは実装上の手抜きでチェックをしていない(やれば実はできるんだけど、コンパイル時間の増大を防ぎたいとか、メモリ使用率を減らしたいとか、まぁいろいろ言い訳をつけてやってない)。

っで、近頃(昨日)R7RSのドラフト7が出て、結構な数の手続きが追加または削除になったのがあり、実際に手続き及びマクロが定義されているかのチェックを目視とかテストケース書いてたらやってられないなぁと思ったのでこんなスクリプトを書いた。
(import (rnrs) (util file)
        (sagittarius vm) (match)
        (srfi :26) (srfi :64))
;; この部分を適当に変える。
;; R7RSだけ調べたいなら"./sitelib/scheme/*"とか
(define files (glob "./lib/**/*"))

(test-begin "bound check")
(define (check-exports file)
  (define (do-check name exports)
    (guard (e (#t 
               (when (message-condition? e)
                 (print (condition-message e)))
               (print "failed to find library: " name)))
      (let ((lib (find-library name #f)))
        (define (check orig export)
          (unless (keyword? export)
            (test-assert (format "~a:~a" name orig)
                         (let ((gloc (find-binding lib export #f)))
                           (and gloc (gloc-bound? gloc))))))
        (for-each
         (lambda (export)
           (match export
             (('rename renames ...)
              (for-each (lambda (rename)
                          (check rename (car rename))) renames))
             (_ (check export export))))
         exports))))
  (guard (e (#t (print "failed to read: " file)))
    (when (file-regular? file)
      (let ((expr (file->sexp-list file)))
        (match expr
          ((('library name ('export exports ...) rest ...))
           (do-check name exports))
          (_ #f)))
      )))
  

(for-each (cut check-exports <>) files)

(test-end)
やっていることは至極単純で、見つけたファイルを読み取って、中からlibraryで始まるS式見つけて、ライブラリ見つけて、exportしているものが存在するか調べる。それだけ。
なぜか、||なシンボルを読まなかったり、キーワードをキーワードとして読み取らなかったりと、変な挙動があるけど、それなりに便利。 既存のライブラリで未束縛なエクスポートを含んでいたのものさえ発見できたし・・・orz

2012-11-10

R7RSの7thドラフト

ようやく出た。正直、出る出る詐欺じゃないかと心配していたw

Ballotの結果なんかは追っているので、もしR7RSで検索してたどり着いたのであればそちらを見ていただきたい。
Sagittariusは6thドラフトに追従していないので5thドラフトから変更となるが、まぁ問題はないだろう。7thドラフトは個人的に割りとインパクトが大きくて、ライブラリの修正だけではなく、コンパイラもいじらないといけない。以下に自分用のメモも兼ねて修正ポイントを記載する。

【コンパイラ】
include-library-declarations
define-library構文に追加になった。include先のライブラリのexport句のみを対象に持ってくる。単なる便利構文。正直、いるかこれ?と思っている。
【ライブラリ】
(scheme base)
いろいろな手続きが削除、移動になった。
(scheme cxr)
cxxr以上の手続きが(scheme base)から取り除かれてこっちに移動。
(scheme char normalization)
Unicodeサポートが必須じゃなくなったので削除。
(scheme division)
たしか6thドラフトの段階で消えてた気がする。
(scheme r5rs)
R5RSコンパチライブラリ。(7thからだったかな?記憶あいまい)
(scheme lazy)
delay-forceが追加になった。
ライブラリの修正は、面倒なだけで特にインパクトは小さいんだけど、コンパイラは面倒だ。まぁ、そこまで面倒でもないんだけど。include-library-declarationsは個人的に非常に醜いと思うのでできれば入れたくないが、たぶんファイルに残るだろうなぁ。

【文字と文字列】
勘違いしていたことと、やはり納得がいかないので一応。
7thドラフトで文字列がサポートしなければならない範囲が明確に定義された。処理系は最低でも「NULLを除いたASCII文字の文字列」のサポートしなければならない。 まぁ、ここまではいいだろう。ただ、「文字列を文字の集合」として捉えた際に現状のドラフトでは不整合がでる。なぜか?問題はこの一文;
All Scheme implementations must support at least the ASCII character repertoire: that is, Unicode characters U+0000 through U+007F.
 7thドラフトでは明確に文字がサポートしなければならない範囲にNULLが含まれているが、文字列ではNULLを省いていいよって言ってるのだ。まぁ、文字列 != 文字の集合なら何の問題もないけど。R7RSに明確に文字列は文字の並び(sequenceってどう訳すの?)と書いてあるね。

2012-11-08

curly-infix (SRFI-105)

I have merged my SRFI-105 implementation on Github to Sagittarius itself. So it can be used from version 0.3.8.
I haven't follow all the discussions nor read all specification (WTF?!), so I'm not sure if the following code is too much #\{ and #\} or it's supposed to be like this;
;; Sagittarius need this line
#!read-macro=curly-infix
;; For portability with other implementations.
#!curly-infix

(import (rnrs))

(define (fact n)
  (if {n = 0}
      1
      ;; Here, it seems too much. If I could write
      ;; fact(n - 1), it seems way better. But my
      ;; implementation (mostly taken from reference
      ;; implementation) doesn't allow me.
      {n * fact({n - 1})}))

{print fact(5)} ;; -> 120
As far as I understood, inside of #\( must be treated the same as usual Scheme way. So if I want to pass calculated argument(s) with infix style, then I need to wrap with extra pair of #\{ and #\}. If I'm not understanding correctly, please let me know :-)

Some part such as {n = 0} might be easier to understand for people who are not so familier with polish notation. However for people who already wrote a lot of Lisp programme, this might make them confused (or maybe not?).

Anyway, providing choices to users is a good thing, I believe.

2012-11-03

SQLiteをODBCで使う

せっかくODBCをサポートしてるんだし(WindowsとCygwinだけだが)、SQLiteでも使ってみるかと思いちょっといじってみた。目的としてはdbi-preparedでバイトベクタとポートを受け付けるようにしたかっただけなのだが・・・

結論を言うと、SQLiteでは(というか、SQLite ODBC Driverでは)BLOBを扱うのが無理。正確には巨大なBLOBを扱うのが不可能くさい。

なぜか?SQLGetDataがここに書かれている動作をしない。(おそらく)呼び出すたびに先頭からデータを取ってくる。もしくは、一度の呼び出しで全部取得できてしまったら二度目の呼び出しでまた全部データを取ってくるため。なので、以下のコードが無限ループに陥った。
(import (rnrs) (dbi))

(define conn (dbi-connect "dbi:odbc:server=SG"))
(define sql "insert into test (id, data) values (?, ?)")

(let ((query (dbi-prepare conn sql 1 "ok")))
  (dbi-execute! query))
(define sql "select * from test")
(let ((query (dbi-prepare conn sql)))
  (print (dbi-execute! query))
  (print (dbi-columns query))
  (let loop ((col (dbi-fetch! query)))
    (when col
      (vector-for-each (lambda (v)
                         (if (binary-port? v)
                             (let loop ((bv (get-bytevector-n v 20)))
                               (unless (eof-object? bv)
                                  (print bv)
                                  (loop (get-bytevector-n v 20))))
                             (display v))) col)
      (newline)
      (loop (dbi-fetch! query)))))
(dbi-close conn)
dataカラムはblobになっている。問題になった、get-bytevector-nの部分。ポートになっているが、中身はSQLGetDataを呼び出して必要なだけ取り出すというもの。ただ、ポートの部分は普通のファイルと共通になっているので(昨日そうした)、まずバッファに溜め込もうとする。問題は、SQLGetDataが常に先頭からデータを読み出すので、EOFが返ることがないこと。

これが、get-bytevector-allだと予定通りに動くのだが、今度はデータが空のblobが半端なくでかいサイズを要求するのでメモリが足りないといわれる。データ空なのに・・・

SQLiteを仕事で使うことはないので、割とどうでもいいのではあるが、解決するため(結局できなかった)に2時間は無駄にしたのでせっかくだしと思い書いた。