(library (foo) (export a) (import (rnrs)) (define a 'a)) (import (rnrs) (foo)) ;;(set! a 'b) ;; uncomment this would also work... (disasm (lambda () (print a)))こんな感じのライブラリをインポートすると
;; size: 5 ;; 0: CONST_PUSH a ;; 2: GREF_TAIL_CALL(1) #<identifier print#user (0x80402708):0>; (print a) ;; 4: RETこんな感じで、
GREF_PUSH
の変わりにCONST_PUSH
が使われるというもの。これだけだと、実はあんまり嬉しくないんだけど、これにコンパイル時の手続き呼び出しが加わるとかなり最適化ができる。例えばaが文字列"1234"
で、スクリプトが(print (string->number a))
だとすると、string->number
がコンパイル時に解決されて数値1234が直接VMインストラクションとして出される。まぁ、そんなに上手いことはまるケースは少ないだろうけど、こういうのは入れておくと後で効いてくるというのが経験側から学んだことなので入れておいて損はないだろう。現状で気に入らないのは
set!
の挙動なのだが、再定義と代入は別ものとしてエラーにした方が精神衛生上いいような気がする。スクリプト上の環境(+evalの環境)は再定義+代入可能にしているのでこれが可能なのだが(#!compatible
をつけても同様)、禁止するとREPL上でも禁止になるという弊害もある。そうは入ってもREPL上で(set! + 'hoge)
とかやるかという話にもなるのだが、例えばMoshとChezはこれを禁止している(psyntaxがかね?)。ちなみに、NMoshとYpsilonは許容している(NMoshは奇妙な挙動をするけど)。ちなみに、再定義もしくは代入された後は定数展開されない。というか、同一ライブラリで定義された場合はされないという話。これは、例えばGambit benchmarkのcompilerみたいなの対策だったりする(展開されると正しい結果が出ない場合)。ただし、
library
及びdefine-library
フォーム内では別のロジックが走るので、展開される可能性はある。でも、よく考えれば、定数しかやらないので展開してしまってもいいかもしれない。後で考えよう。追記
いや、やっぱり同一ライブラリ内では駄目だな。以下のようなのが困る。
(define b 'b) (define (foo) (print b)) (foo) (set! b 'c) (foo)これを定数展開してしまうと、二度目の
foo
が正しく動かない。追記2
インポートされた束縛に対する
set!
は禁止することにした。再定義はOK。そういえばこの変更でR6RS/R7RS準拠のevalが書けなくもないのだが(定義禁止等)、まぁそこまでする必要は今のところないかなぁ。
No comments:
Post a Comment