Let's start Scheme

2013-02-21

リモートREPLとTLS

別に今のところ使う必要がないのだけど、あると便利かなと思ってリモートREPLを作ってみた。最初はYpsilonのtrunkにあったような簡単なのにしてたんだけど、そうするとreadがエラー投げまくりだったので、もう少しまともなプロトコルを使うようにした。以下のような感じで使える。

;; server side
(import (sagittarius remote-repl))
(define auth (make-username&password-authenticate "test" "test"))
(define server (make-remote-repl "5000" :authenticate auth))
(server)

;; client side
(import (sagittarius remote-repl))
(connect-remote-repl "localhost" "5000")

リモートでいろいろやれるようになるとそのうち便利なことがあるだろうけど、当然問題もでてくる。認証とセキュリティの問題がまずあがるだろう。認証はコードを見ればなんとなく分かると思うけど、ユーザ名とパスワードの認証が実装してある。:authenticateキーワードを省略すると認証なしになる。

さて、セキュリティの問題だが、通信を覗かれるとこのままでは丸見えである。そこで今まで割りと放置気味だったTLSのサーバーソケットを急いで実装した。使い方は以下のようになる。
(import (rnrs) (rfc tls) (rfx x.509))
(define certificate (call-with-input-file make-x509-certificate :transcoder #f))
(define server-socket (make-server-tls-socket "5000" (list certificate))
;; (rfc tls) exports server-accept method so that
;; user can use it for both usual socket and TLS socket.
(let ((socket (server-accept server-socket)))
  ;; so is call-with-socket
  (call-with-socket socket
    (lambda (socket)
      ;; do whatever
     )))
クライアント側がDHEなプロトコルを実装しているなら秘密鍵は要らない。DHE以外のCipher specも使いたいなら秘密鍵を:private-keyで渡してやる必要がある。

TLSの実装はかなりと汚いのでソースを見たい人は覚悟してほしい。そのうちリファクタリングとかまじめにステートを追うようにする予定(今はクライアント、もしくはサーバーが正しい順序でパケットを送ると仮定している)。

この辺まで作ってといざリモートREPLをTLSでと思ったときに証明書がないことに気づいた。(手元にあるときはそれを適当に使っていた)。なので(rfc x.509)ライブラリに簡単なX509証明書生成手続きを足そうかと考え中。鍵対生成は既にあるので。

2013-02-16

メモリ使用量

Sagittariusはかなりのメモリ喰いである。割と富豪的にメモリを使用するように書いてあるのでしょうがないのだが、テストを走らせるとメモリ不足で落ちることがしばしばある。実際の用途でこんなにメモリを大量に使うプログラムをまだ書いたことがないので、問題になるのかどうかも分からないのだが、精神衛生上よろしくないということで多少何とかしようと重い腰を上げたところである。

とりあえず分かっているところから潰していくべきだろうと思い、ライブラリのルックアップ用に確保してある部分を削ってみることにした。現状ではSagittariusはインポート時に全てのリネーム、プリフィックス等を解決して、それらをライブラリのバッファに確保している。その方が楽だったのと、たぶん高速だと思ったからである。っが、このバッファたとえば(rnrs)をインポートすると、リネームしてもしなくても全てのシンボルがalistとして保管されるという極めて燃費の悪い仕様になっている。

そこで、特に何も指定せずにインポートした際はバッファを作らないように変更してみて、どれくらいメモリ使用量が減るかを検証してみた。(まだ全部は動いてない・・・)
以下が(import (rnrs)) だけ書いたスクリプトを流した結果。
$ ./build/sash.exe -s -c test.scm

;; Statistics (*: main thread only):
;;  GC: 5591040bytes heap, 11359562bytes allocated, 8 gc occurred

$ sash -s -c test.scm

;; Statistics (*: main thread only):
;;  GC: 5591040bytes heap, 10980426bytes allocated, 8 gc occurred

$ ./build/sash.exe -s test.scm

;; Statistics (*: main thread only):
;;  GC: 4190208bytes heap, 6942600bytes allocated, 7 gc occurred

$ sash -s test.scm

;; Statistics (*: main thread only):
;;  GC: 5591040bytes heap, 7885440bytes allocated, 6 gc occurred
交互にキャッシュなし、キャッシュありで試している。キャッシュなしの場合はキャッシュを作成する必要があるのとかがあいまってあまり違いはない(むしろ変更後の方が多少悪い)。しかし、キャッシュがある場合を見ると、GC回数は増えているがヒープ、アロケートともに1MB程度減っている。

もちろん、今まで使っていた部分を削ったのだから減るのは当たり前なのだが、効果としてはそこまで大きくないような感じがする。ちなみに、(rnrs)をインポートすると20以上のライブラリがインポートされる。つまり、20以上のライブラリからバッファを削ったところで1MB程度しか抑えられないということだ。テストはおそらく合計で200以上のライブラリを作成することになるので、単純計算では10MB以上は消費量が抑えられる計算になるが、どうしたものだろうか。実行速度にどれくらいの影響があるかも見てからかな。

2013-02-15

Sagittarius Scheme 0.4.2リリース

Sagittarius Scheme 0.4.2がリリースされました。今回のリリースはメンテナンスリリースです。また、R6RS準拠度が上がっています。
ダウンロード

修正された不具合
  • エクスポートされたシンボルが特定の場合に不可視になる不具合が修正されました。
  • マクロ展開器がライブラリのスコープを壊す不具合が修正されました。
  • datum->syntaxが未束縛のシンボルに対して動作しない不具合が修正されました。
  • syntax-case内で局所マクロを定義するとコンパイル時エラーが発生する不具合が修正されました。
  • コンパイルキャッシュがSEGVを起こす不具合が修正されました。
  • environment手続きが0個の引数を受け付けない不具合が修正されました。
  • integer->bytevector手続きにオプション引数を与えた際に非正確な値が返る場合がある不具合が修正されました。
  • プリフィックスをつけてインポートした際に予期しないシンボルに変換される不具合が修正されました。
  • make-variable-transformerが可視なシンボルを不可視にする不具合が修正されました。
  • exact-integer-sqrtに巨大数を与えると無限ループする不具合が修正されました。
  • #!でモードを指定していないライブラリをロードした際に予期せぬコンパイル結果を起こす不具合が修正されました。
改善点
  • (expt 2 x)のパターンが最適化されました。
  • マクロ展開器が可能な限り先にマクロを展開するようになりました。
  • 未束縛な識別子をコンパイラが検出し、R6RSモードなら&undefinedを互換モードなら警告を出すようになりました。警告は-Ewarn以上のオプションをつけて起動した場合に表示されます。
  • bytevector-fill!手続きがオプション引数startとendを取るようになりました。
新たに追加された機能
  • crc32及びadler32手続きが(rfc zlib)に追加されました。
  • split-key、combine-key-components及びcombine-key-components!手続きが(crypto)に追加されました。
  • ->odd-parity手続きが(util bytevector)に追加されました。
  • (tlv)ライブラリがDGIスタイルのTLVをサポートするようになりました。
新たに追加されたライブラリ
  • バイナリパック、アンパックライブラリ(binary pack)が追加されました。
非互換な変更点
  • 実行時の未束縛なシンボルに対するエラーが&assertionから&undefinedに変更されました。

2013-02-11

マクロ展開

Sagittariusのマクロ展開のタイミングを他のtoplevelと同じではなく、多少先んじて行うようにしたのだが、その際にふと以下のコードはR6RS的にはOKなのか気になった。
#!r6rs
(library (foo)
    (export d)
    (import (rnrs))

  ;; def ;; <- NG?
  (define-syntax def
    (lambda (x)
      (syntax-case x ()
        (k
         (with-syntax ((d (datum->syntax #'k 'd)))
           #'(define d 'ok))))))
  def ;; OK
  )

#!r6rs
(import (rnrs) (foo))
d
NG?っとマークが打ってある行なのだが。OKのマークの行は正しく動くべきだと思っているのだけど(Ypsilonはエラーになった、ChezはOK、Moshは現在手元にない)、マクロの展開がコンパイルされる前に行われるのであれば、NG?の部分もvalidな気がするんだけど、どうなんだろう?(ちなみに、パターンの部分を普通のマクロ(?)形式にすると当然Ypsilonでも動く。)

正直これがだめでも困ることはないんだけど、現状の実装ではこれをはじくことができない(実装方針は一つ前の投稿に書いてあるのでそっちを見て) 。ものすごく頑張ればいけるような気もするけど、割とどうでもいいエラー処理な気がするのでたぶん放置すると思う。問題になってから考えればイイ系。

マクロ展開のタイミング

R6RSポータブルなコードを書く際に、書く処理系の癖を熟知してなんてことよほど暇じゃないと出来ないだろうということに気付いた。現状で一番問題になっているのはマクロ展開フェーズ周りの処理である。

Sagittariusはマクロ展開フェーズなんてまどろっこしいものを持たないのだが、なんとかしてそれっぽくエミュレートできないかということちょっと知恵を搾り出し中(正直、搾取しすぎて搾りかすしか出ないが・・・)。とりあえず、library内のtoplevelなdefine-syntaxについては強引に先にやってしまうおう的に解決。(一度library内のtoplevelにある式を全部舐めて、define-syntaxだけ先にコンパイルするという割とお粗末な解決方法。なので、マクロ展開後に出てくるdefine-syntaxとかは対応してない。やろうと思えば出来る気もするが・・・)。頑張ってもう少し賢くした。R6RSより多少制限がゆるいけど。

問題になってくるのは局所マクロである。

R6RSでは以下のようなコードが動かないといけない。
(import (rnrs))
(let ()
  (define-syntax define-inline
    (syntax-rules ()
      ((_ (name . args) body ...)
       (define-syntax name
         (syntax-rules ()
           ((_ . args)
            (begin body ...)))))))

  (define (puts args) (display args) (newline))
  (define-inline (print args) (puts args))
  (print "abc"))
;; abcを表示

(let ()
  (define-syntax define-inline
    (syntax-rules ()
      ((_ (name . args) body ...)
       (define-syntax name
         (syntax-rules ()
           ((_ . args)
            (begin body ...)))))))
  (define-inline (print args) (puts args))
  (define (puts args) (display args) (newline))
  (print "abcde"))
;; abcdeを表示
最初のパターンは何とか(そこそこスマートに)解決できたんだけど、次のパターンのうまい方法が思いつかない。

起きていることそしては、define-inlineで展開されたprintはputsが内部defineとして保持される前にコンパイルされる。そうするとprintのマクロ展開器はputsが内部defineだと知らないので展開器は特に環境情報を付加することなくシンボルputsを識別子に変換する。っで、コンパイラは何も持ってない識別子を大域変数と解釈しコンパイルする。

コンパイラが上記のputs識別子から内部defineのputsを参照できればいいのだが、本当に何の情報も持っていない識別子の参照を許すとマクロのhygieneを壊してしまうのでうかつなことはできない。さて、どうしたものか・・・

2013-02-08

make-variable-transformerに潜む罠

正確には、潜んでいる(進行形)か、潜んでいた(過去形)の方がいいのだろうか?

コード見て原因を確認していないんだけど、動き的に何が起きているのか分かったのでとりあえず書く。問題になるのは以下のコード。
#!r6rs
(import (rnrs))

(define-syntax pack
  (make-variable-transformer
   (lambda (x)
     (syntax-case x ()
       ((_ fmt vals ...)
        #'(begin vals ...))))))

(define-syntax define-crc
  (lambda (x)
    (syntax-case x ()
      ((k)
       (with-syntax ((crc-finish (datum->syntax #'k 'crc-finish)))
         #'(define (crc-finish r) r))))))

(let ((a 1))
   (define-crc)
   (pack "<C" (crc-finish a)))
上記のコードは0.4.1では実行時に、0.4.2(HEAD)ではコンパイル時にunbound variableエラーがでる。何が問題かと言えば、おそらくvariable transformerは実行時の環境ではなくマクロ捕捉時の環境で入力式をラップしているのが問題のはず。解決策は実行時環境でラップすればいいだけだと思うのだが、何でマクロ捕捉時の環境使っているのか確認しないといけない(推測が正しければ)。

さて、上記のコードは再現できる最小規模のコードなのだが、このバグを発見できたのはコンパイラが未束縛の識別子を検出するようにしたからである。もともとR6RS的にはコンパイル時に検出してエラーにしなければいけないんだけど、面倒だなぁと思ってサボっていた。っで、いろいろいじっているうちに下地が整ったというか、なんか簡単に検出できるようになっていたので、えいやっと実装してみたという感じである。
実装自体は非常に簡単だったのだけど、問題は既存のライブラリからごろごろと警告が出てきたので全て修正する方が大変だったことだろう。総称関数とか微妙に挙動を変えないとどうしようもないなぁとかだったし。

何はともあれこの修正でマクロ周りのバグがコンパイル時に発見できるようになったのでいろいろ便利になったと思う。

2013-02-06

define-constant

MessagePackのR6RS Scheme用ライブラリを書いてたときにふと思いついたマクロ。
(import (for (rnrs) run expand)
        (for (rnrs eval) expand))

(define-syntax define-constant
  (lambda (x)
    (define (eval-expr k expr)
      (if (pair? (syntax->datum expr))
          (let ((r (eval (syntax->datum expr) (environment '(rnrs)))))
            (datum->syntax k r))
          expr))
    (syntax-case x ()
      ((k name expr)
       (with-syntax ((value (eval-expr #'k #'expr)))
         #'(define-syntax name
             (lambda (y)
               (syntax-case y ()
                 (var (identifier? #'var) value)))))))))

(define-constant const-1 1)
(define-constant const-2^16 (expt 2 16))

(display const-1) (newline)
(display const-2^16) (newline)
仕組みは簡単で、マクロとして束縛しつつ、define-constantマクロ展開時にexprを実行しておいてしまおうというだけのもの。込み入ったことはできないけど、(expt 2 16)とか見たいな(rnrs)の中だけで済ませられるものに対しては有効ではある。

Sagittariusには構文としてdefine-constantがあって、それで定義されたものはコンパイル時に定数として畳み込まれるんだけど、MoshやYpsilonにはない。しかも、2^16とかいいけど、もっとデカイ数字になったときにわざわざ数値リテラルで嫌だなぁと思い書いてみた。結局使ったの(expt 2 32)までだけど・・・

これと似たようなので、Vicareの中の人がよく使っている、define-inlineってのがある。こういう、コンパイラを当てにしないコードってポータブルなコードを書くときにはよく使われるのだろうか?

2013-02-05

適用可能なマクロ

2chのLisp Schemeスレでsyntax-caseを毛嫌いしているカキコを見たので、こんなことが出来るのはsyntax-caseだけなんてのでも書いてみようかと思った、だけ。
実際、覚えるまでは「なんでこんなもの」なんて思ってたけど、実装者泣かせなだけで使用する分にはこの上なく便利だと思うし、毛嫌いされる理由の一つに「サンプルが少ない」というのがある気がするので、その解消になればとも思う。実際、syntax-rulesだって十分複雑じゃね?とも思うが、こいつはR5RSからのサンプルが結構あるから慣れたというのが大きい気がする。(未だに、複雑なパターンマッチは書けないけど...)

とりあえず以下が便利そうに使えるのではないかと思われるコード片。
(import (rnrs))

(define (format** . args) ...)

(define-syntax format*
  (lambda (x)
    (syntax-case x ()
      ...)))

(define-syntax format
  (lambda (x)
    (syntax-case x ()
      ((_ fmt args ...)
       (string? (syntax->datum #'fmt))
       #'(format* #f fmt args ...))
      ((_ port fmt args ...)
       (string? (syntax->datum #'fmt))
       #'(format* port fmt args ...))
      ((_ . rest)
       #'(format** . rest))
      (var (identifier? #'var) #'format**))))
さすがに中身までは実装してないけど、見た目で何をしているのかなんとなくは分かってもらえると思う。formatはエントリポイントで、そこから引数に応じて適切にdispatchするという寸法。format文字列を動的に作るってのは(やらないわけじゃないけど)かなり少数派なのでマクロ展開時文字列だと分かっているのであれば展開してしまおうという寸法。
また、syntax-caseでは最後のパターンのように「残り全て」みたいな書き方ができるので、その際にformat単独で現れているのであれば、下請け手続きを返すみたいなことをすれば以下のようにapplyにも渡すことができる。
(apply format "~a" 'a '())
これは単純に、以下のように展開されるだけ;
(apply format** "~a" 'a '())
identifier?でチェックしているのは、そうしないと妙なものまで使えるようになってしまうから。(まぁ、上記の場合だと、3つ目のパターンではじくはずだけど)

この手法はIndustriaの(weinholt struct pack)ライブラリで使われていて、ちょっと目から鱗だった。

「適用可能なマクロ」なんて書いたけど、実際にapplyで使えるわけではなく、「そう見せかける」だけである。っがこれを使えばCLにあるコンパイラマクロっぽい挙動が可能になるので処理系のコンパイラに依存したくないとか、使ってる処理系の最適化がしょぼいとか、逆にここを展開できれば処理系の更なる最適化が期待できるなんてときにはいいかもしれない。
もちろん、弊害もある。マクロ版の実装と手続き版の実装の2つをメンテしないといけないこと。上記の例なら、format文字列になにか新しいのを追加したいとなった際に、書き方にもよるだろうが、両方をいじる必要がでるかもしれない。

2013-02-02

最近読んだ本


 PRAGUE FATALE: A BERNIE GUNTHER THRILLER

8月くらいに買った本を今年の1月に読み終えるというていたらくっぷり。

物語は1941年のドイツ、ベルリンでオランダから単身赴任していた男が殺害されていたところから始まる。
話としてはナチス将校のHeindrichが、オランダ人殺人事件を追っていたベルリン警察刑事のGuntherをボディーガードとしてチェコのプラグに呼び、といった感じでつながっていく。個人的には、殺害されたオランダ人の伏線の回収がちょっと強引じゃないかなぁ?とか、途中でたぶんこれ殺したのあの人だなぁ(コナン風)ってなんとなく分かっちゃったりとかしたけど、全体をみれば面白いと思う。Heindrichが僕の中で人のいいおじいさんでイメージされてたのが、最後になってやっぱり典型的なナチス将校になったというのもあるが。どこを読み間違えたんだ?

なぜ読むのに5ヶ月もかかったかといえば、かなり断続的に読んでいたというのもあるんだけど、1941年当時のドイツとかチェコとかの情勢が全くイメージできずにいまいち話にのめりこめなかったというのが大きい気がする。(英語を読むのが遅いというのもあるが、それでもまともに読めば10日から20日かからないくらいで読める)。そういう意味では当時の情勢が分かっているともっと楽しめたかもしれない。ペーパバックの表紙に'UTTERLY CONVINCING'なんて書いてあったりするのだから、いろいろリアルに書いてあるんだと思う。

2013-02-01

How to add Twitter widget to blogger

It took a day to realise how to make it work properly for me. So there might be loads of people who want to add but somehow it didn't work.

This site describes the basic process really well and it's really useful.
How To Add Twitter Updates Widget in Blogger Blogs - HOWTOQUICK.net

The problem I've got was I could see it on your preview but I didn't see it in real one. If you are living in the US or somewhere using .com you don't have this problem. But if you are living somewhere doesn't use it (.nl for example), you have the problem. Blogger redirects viewers' country specific domain automatically.

The workaround is really simple. Just add viewers' domain to 'Domain' text area. For example, if you want to show your tweets people from Japan, then add ${your blog name}.blogger.jp.

I'm not sure if there is a generic solution for this problem and so far I couldn't find it. If you have solved this with clever way, please let me know.