齊藤
趣味プログラマ。 気の向くままに思い付きのままにコードをいじって遊んでます。
関連リンク
私の今迄の疑問。
そして要望。(解決済み)
混ぜるな危険
数値文字参照の扱いが htmlprag と ssax で違ってた。
htmlprag ではこんな感じ。
(html->sxml "<html><body><div>㊌</div></body></html>") => (*TOP* (html (body (div (& 12940)))))
ssax ではこんな感じ。
(ssax:xml->sxml (open-input-string "<html><body><div>㊌</div></body></html>") '()) => (*TOP* (html (body (div "㊌")))) ;; gauche-charactor-encoding が utf-8 以外のときはどうなるんだろう?
htmlprag で作った sxml を sxml:sxml->xml で変換しようとするとエラーになってしまう。 shtml->html を使えばいいだけの話ではあるけど、同じ sxml 関連だからと思ってこういう風に出自の違うライブラリを混ぜて使ってると予想外のところで躓いてしまうこともあるという教訓。
(2011/02/11 07:39:49 PST)
- Shiro(2011/02/11 14:19:48 PST): これは歴史的経緯かなあ。パーズ時に実体参照を解決 するssaxの方が理念としては正しいんですが、R6RS以前は処理系がascii外の 文字をサポートしている保証が無かったんで、ポータブルにしようとすると すべてを文字にしてしまうことはできなかったんですね。 ちなみにgauche-character-encodingがutf-8でない場合ですが、 数値実体参照解決は「unicodeコードポイントから文字へのマッピング」なので、 その文字に対応する内部エンコーディングの文字があればそれに、 無ければ代替文字(下駄)になると思います。
- 齊藤(2011/02/17 05:05:12 PST): なるほど。 言われてみれば「数値文字参照」は「文字」なのですから特別扱いは変ですね。 ライブラリとしては隠蔽するのがスジということで納得できます。
文字列ポート
http://practical-scheme.net/chaton/gauche/a/2011/02/10#entry-4d542b4d-9493d
齊藤 2011/02/10 10:15:41 PST ところで話は変わりますが、 output string port に蓄積したものを今度は input string port として読み出したいってケースは珍しくないと思うんですが、 get-output-string で取り出して open-input-string するしか無いですか? なんとなく非効率な気がするんですが、内部的には共有されてたりするのかな。
shiro 2011/02/10 10:53:36 PST ああ、get-output-stringはコピーが発生しちゃいますね。output string portは最終的な長さがわからないのでチャンクのリストの形でデータを保持してるので。今のところその蓄積されたデータを得る方法はget-output-stringだけです。
procedural buffered portを使ったらパイプのような対のポートを作れるんじゃないかな。
齊藤 2011/02/10 11:15:24 PST <buffered-output-port> を使えば案外簡単に出来そうな予感。
と言うわけでやってみた。
まずは出力ポートから。
(use gauche.vport) (use gauche.uvector) (define-class <ostring> (<buffered-output-port>) ((chunks :init-keyword :chunks) (last-chunk :init-keyword :last-chunk))) (define-method initialize ((obj <ostring>) initargs) (let1 chunks (cons #f '()) (next-method obj (list* :chunks chunks :last-chunk chunks :flush (lambda (buffer flag) (let1 lc (~ obj 'last-chunk) (set-cdr! lc (cons (u8vector-copy buffer) '())) (set! (~ obj 'last-chunk) (cdr lc)) (u8vector-length buffer))) initargs )))) (define (make-open-ostring . opt) (apply make <ostring> opt))
試しに動作させてみると…
(define a (make-open-ostring :buffer-size 2)) ;; ←効果を見易くするためバッファを小さく (write '(hoge huga hige) a) (flush a)
このとき a に蓄積されているデータの様子は
(~ a 'chunks) ;; => (#f #u8(40 104) #u8(111 103) #u8(101 32) #u8(104 117) #u8(103 97) #u8(32 104) #u8(105 103) #u8(101 41))
入力ポートを作るコードはこんな感じ。
(define-class <istring> (<buffered-input-port>) ((chunks :init-keyword :chunks))) (define-method get-input-port ((obj <ostring>)) (let1 x (make <istring> :chunks (cdr (~ obj 'chunks))) (set! (~ obj 'chunks) (cons #f '())) x)) (define-method initialize ((obj <istring>) initargs) (next-method obj (list* :fill (lambda(buffer) (let* ((obl (u8vector-length buffer)) (chunks (~ obj 'chunks)) (ib (if (null? chunks) #f (car chunks)))) (if (not ib) 0 (let ((ibl (u8vector-length ib))) (cond ((= ibl obl) (u8vector-copy! buffer ib) (set! (~ obj 'chunks) (cdr chunks)) ibl) ((< ibl obl) (u8vector-copy! buffer ib) (set! (~ obj 'chunks) (cdr chunks)) ibl) ((> ibl obl) (u8vector-copy! buffer ib) (set-car! (chunks (u8vector-copy ib obl))) obl)))))) initargs )))
先程の出力ポートから入力ポートを作って入力してみる。
(define b (get-input-port a)) (read b) ;; => (hoge huga hige)
入力ポート内での状態管理が手抜きなので無駄にコピーしちゃってるけど、とりあえずは機能してるみたいなので良しとしよう。
(2011/02/10 13:12:25 PST)
モジュールとメソッド
curl バインディングを使ってみた。
<curl> は url というスロットがあり、そのアクセサは url-of なのだけれど、 url-of は export されていないせいか curl モジュールを use しただけでは見えない。 (define-generic url-of) (use curl) としてみても url-of ⇒ #<generic url-of (0)> だった。
curl モジュールの中では initialize メソッドを定義しているようで、これも export はされていないのに curl モジュールを use しただけでちゃんと機能しているようだ。 initialize は特別扱いなんだろうか。
メソッドとモジュールの関係がよくわからない感じ。
(2010/01/23 08:48:23 PST)
- Shiro(2010/01/23 18:14:52 PST): いえ、initializeを特別扱いしているわけではなく、 initializeの束縛がcurlモジュールとそれをuseしているモジュール両方から 見えているということが決め手なんです。url-ofの場合、上のように定義してもその 束縛がcurlモジュールから見えないため、curlモジュールはその中で独自に 束縛を作ってしまいます。 これはずっと前からの未解決問題でして、Gauche:GenericFunctionとModuleで議論しています。 今のところ綺麗な解決策が見えないので、:getter, :setterによるアクセサの使用は推奨しません。 スロットアクセスは(ref obj'url)でいけますし、 0.9.1からは(~ obj'url)と書けるようになるのでもっと短くなります。 アクセス時のチェックが欲しければgauche.mop.validatorのように スロットアクセスの下の層にコードを挿入することが可能ですし。
ユニークなシンボル
CommonLisp には gensym があって、gensym は Gauche でも使える。 r6rs の範囲内で gensym を書くことは出来るだろうかということを考えた。
本来の gensym は intern されないシンボルを作るものだ。 さすがに言語組込の拡張機能として用意しなければ書けないだろうとは思ったのだが、色々と考えている内にこんなものが出来上がった。
(define (my-gensym) (car (syntax->datum ((lambda(x) (syntax-case x () ((_ a) (generate-temporaries (syntax (a)))))) #'(_ _)))))
これが gensym として使えるとは思ってない。 しかし、遊んでいる内に気付いたことがある。
(define g (my-gensym)) (define h (string->symbol (symbol->string g))) (write (eq? g h))
r6rs 的にはスペルが同じシンボルを比較すれば eq? になるはず。 なので、ここでは #t が返るのが正当だと私は思う。 実際、Mosh, plt-scheme, petite-chez-scheme ではそうなる。 でも、 Ypsilon では #f だった。
(2009/12/08 03:10:48 PST)
- leque (2009/12/14 21:32:08 PST): Clojure の syntax quote でも同じような結果になりますね。
(defn my-gensym [] (first `(sym#))) (let [s (my-gensym)] (= s (symbol (str s)))) ;; => true
と思ったら標準の gensym がそもそも intern しているという(core.clj)。(let [s (gensym)] (= s (symbol (str s)))) ;; => true
contextual information
schemeのマクロは難しい。datum->syntaxの第一引数の意味がまだ良くわからない。文脈情報ってなんやねんという感じ。今は名前解決の起点と理解しているけれど、これでいいのだろうか。 (2008/04/08 07:07:16 PDT)
- leque(2008/04/09 18:17:39 PDT): R6RS:翻訳:Standard Libraries:12.6 Syntax-object and datum conversions の datum->syntax の説明によると、「この構文オブジェクトは template-id が挿入されたのと同時にコードに挿入されたかのようにあつかわれる。(with the effect that the syntax object behaves as if it were introduced into the code when template-id was introduced.)」とあります。コードで例を示すと、
(define-syntax s1 (lambda (form) (syntax-case form () ((ctx) #`(let* ((x 3) (y #,(datum->syntax #'ctx 'x))) (* y y)))))) (define-syntax s2 (lambda (form) (syntax-case form () ((ctx) #`(let* ((x 5) (y #,(datum->syntax #'k 'x))) (* y y)))))) (display (let ((x 4)) (s1))) ; =| 16 (newline) (display (let ((x 4)) (s2))) ; =| 25 (newline)
s1 の方は ctx = s1 の導入されたスコープ(= s1 の静的スコープ)の x を参照するようになるので、ここの x は (display (let ((x 4)) (s1))) の方の x で x = 4 になります。一方、s2 の方では syntax-case 内で新たに導入された識別子 k と同じスコープの x、つまり y の直前に let* で束縛された x になるので (let* ((x 5) ...) ...) の x で x = 5 になり、各 display はそれぞれ 16 と 25 を表示します。たぶん、 R6RS の説明が一番簡潔でわかりやすいんじゃないかと思います。 datum->syntax の説明の最後には syntax-case で簡易 define-macro を書く、みたいな例もあります。 - 齊藤(2008/04/09 21:26:59 PDT):スコープを変える、というのはおおよそ理解できたと思いますが、別な部分で理解が不十分なようです。以下のようなケースで躓きました。
(define-syntax expand/scope (lambda(x) (syntax-case x () ((k scope body) (with-syntax ((p (datum->syntax #'scope (syntax->datum #'body)))) #'p))))) (let foo ((a 3)) (let bar ((a 2)) (let ((a 1)) (expand/scope bar a))))
letで導入したbarと同じスコープでaが解釈されて3になることを期待したのですが、結果は1となります。scopeにマッチしたbarはletで導入したbarでなくexpand/scopeが起動したときに導入されたことになるのでしょうか。このへんの理屈がわかっていません。 - leque(2008/04/10 06:40:43 PDT): うーん、このあたりは私も理解が不十分なのですが、導入された「スコープ」ではなく、やはり導入された「文脈」という表現を使った方がよさそうな気がしてきました。ここで a が 1 になるのは、 bar の導入された「文脈」において、 expand/scope の a の属する静的スコープに従って a を評価するから 、1 になるんじゃないかなあと思います(識者の意見求む)。
- 齊藤(2008/04/10 17:34:21 PDT):以下のように変形すると2が返ってきました。
(let foo ((a 3)) (let bar ((a 2)) (let-syntax ((expand/bar-scope (lambda(x) (syntax-case x () ((k body) (with-syntax ((p (datum->syntax #'bar (syntax->datum #'body)))) #'p)))))) (let ((a 1)) (expand/bar-scope a)))))
この結果からすると「bar の導入された『文脈』において、 expand/scope の a の属する静的スコープに従って a を評価する」というわけでもないようです。先の例ではscopeにマッチしたbarがある場所でaが解釈されて、barが導入された場所とは解釈されないのではないか、なんて思ったりしているのですが、なんだか根本的な勘違いがある気もしてどうにもスッキリしないですね。- leque(2008/04/12 01:11:11 PDT): この場合だと bar はパターン変数ではないので、 a はマクロ定義時の環境を参照しているため 2 になるのだと思います。試しに #'bar を #'k にすると 1 になります。
- わたしは「datum->syntaxの第一引数のidentiferがリスト上に存在する位置で第二引数のdatumを変換する(変な表現ですがうまい書き方をまだ思いつきません)」と解釈しています。 先のリストでは(syntax scope)によりパターン変数scopeから取り出されるidentifierはbar、それがリスト上に書かれているのは(let ((a 1)) ...)の内側です。なので挿入されるaは1に束縛されたaとなり、次のリストでは(syntax bar)により生成されるidetifierはbar、それがリスト上に書かれているのはaが2に束縛されているのと同じスコープ(named letなので)。なので挿入されるaは2に束縛されたa。といった感じです。しかし、ここで疑問に思うのは、なぜ最初のリストのbarがnamed-letのbarを参照して2を返すという仕様になっていないかということです。これにはなにか理由がありそうで、今考えています。追記:あ、またやってしまいました。datum->syntaxの第一引数がパターン変数でないときの動作は思っていたものと違うようです。これも要調査になりました^^;
define-macro
syntax-caseマクロはハイジニックなマクロもそうでないマクロも書けるということで、そんならsyntax-caseでdefine-macroを模倣できるんではないかとふと意味も無く思いついて書いてみた。
(define-syntax define-macro (lambda(x) (syntax-case x () ((_ name body) (with-syntax ((syntax-body (datum->syntax-object (syntax k) (syntax-object->datum (syntax body))))) (syntax (define-syntax name (lambda(y) (syntax-case y () ((name a (... ...)) (let* ((m (syntax-object->datum (syntax (a (... ...))))) (n (eval `(syntax-body ,@m) (interaction-environment)))) (with-syntax ((r (datum->syntax-object (syntax k) n))) (syntax r)))))) )))))))
なにやらあまりにもごちゃっとしているし、途中でevalしてるどころか(r6rsでは廃止されたはずの)interaction-environmentを使ってるしで無様なものになってしまった。既存の似たようなことをしたコードが無いものかと"(define-syntax (define-macro"を検索ワードとしてぐぐってみると、まさに同じことをやろうとしているコードが見つかった。
http://c2.com/cgi/wiki?DefineSyntax
こういう書き方もあるんだなぁ。 datum->syntax-objectの第一引数の意味をさっぱり理解できてなくてSchemeを使用したメタプログラミングの記事にある「ちょっとした魔法の式です」をとりあえず鵜呑みにして(syntax k)と書いているので、まじめに理解に努める必要アリと思われる。(2008/01/09 07:13:40 PST)
- これについてはあまり解説をみかけないですね。でもdatum->syntax-objectの第一引数はとても重要なのです。たとえば上のdefine-macroの定義だと
(define-macro foo (lambda () '(list x))) (let ((x 10)) (foo))
はエラーになりますが、最後の(syntax k)を(syntax name)に書き換えると(10)に評価されます。
こっちのほうがdefine-macroっぽいですね。以上通りすがりでした ^^b
- 齊藤(2008/01/12 23:54:50 PST):第一引数が意味するのは第二引数が解釈されるべき文脈(?)みたいなものだというのは分かってきました。srfi 93を印刷してじっくり読もうとしたら組版に凝ってしまい読む段になって面倒になってきたりも。本当はR6RSを読んだ方がいいんでしょうが、そのへんの日本語訳がみあたらないし、日本語の解説文は見当たらないしでやっぱ英語わからないと不利ですよねぇ。
グタ
閉じ括弧をコッカと呼ぶのがアリならXMLの閉じタグはグタと読んでもよさそうなもんだ。
(2006/11/02 21:57:20 PST)