fluid-let

scheme標準ではないものの、Gaucheにも入っているマクロfluid-letがちょっと気になっていた。ドキュメントには「動的スコープの変数をエミュレートするマクロです」と書いてあるのでどんな黒魔術なんだろうと思っていたのだが、独習scheme3週間にある実装例を見ると実に簡単なものだった。私は伝統的マクロがいまひとつわかっていないので、理解のためsyntax-caseで書き直したものがこれ。

(define-syntax fluid-let
  (lambda(x)
    (syntax-case x ()
      ((_ ((var obj) ...) body ...)
       (with-syntax
           (((temp ...)
             (generate-temporaries (syntax (var ...)))))
         (syntax
          (let ((temp var) ...)
            (dynamic-wind
                (lambda() (set! var obj) ...)
                (lambda() body ...)
                (lambda() (set! var temp) ...)))))))))

syntax-rulesでも書けるはずだけれど、一時変数を必要な数だけ作成するのに再帰するのが面倒でgenerate-temporariesを使いたかったという理由でsyntax-caseを使っている。
で、以上の定義でドキュメントに記載されている例の通りには動く。

(define x 0)
(define (print-x) (print x))

(fluid-let ((x 1))
  (print-x))  ;; -> 1を表示

しかし、動的変数のスコープをエミュレートというからには以下の例でも機能して欲しいものなのだが、そういう実装は可能だろうか?(多分無理)

(define print-x
  (let ((x 0))
    (lambda()
      (display x))))

(fluid-let ((x 1))
  (print-x)) ;; ->上の実装では0を表示するが1を表示して欲しい

ここまで書いて思い出したのがJavaScriptにおけるスコープチェーン(名前連鎖)のつなぎかえだ。JavaScriptでは名前を解決するためのスコープをプログラマがつなぎかえることが出来るという変態的機能があるのだ。__parent__プロパティは親となる環境へと繋っており、これに代入することで実現する。

function fluidLet(vars, proc) {
   var t=proc.__parent__;
   proc.__parent__ = vars;
   var u=vars.__parent__;
   vars.__parent__ = t;
   proc();
   proc.__parent__ = t;
   vars.__parent__ = u;
}

printx=function(){
   var x=0;
   return function(){print(x)};
}();

fluidLet({x:1}, printx);
printx(); // -> 1を表示する

と、思ったらSpiderMonkeyでもFirefoxでも期待通り動作しなかった。__parent__プロパティは今は読み込み専用になっているそうだ。元々非標準であるし、それは仕方無いとは思うのだが、これだけでなく、最近ではリフレクションに類するものはほぼ全面的に非推奨という扱いになっている模様。次あたりの規格では無くなるかも。JavaScriptで特に面白いところなのに。
Document ID: 846ee4ae04e04113bd874b3996b39714