https://gist.github.com/4357479 で「ダイナミックスコープな変数であるかどうか」をプログラムから(というかreplから)知る方法について調べた経緯です。
*print-readably*
を対象にして調べる途上で出会ったいろいろなものについて、とりとめなくgdgdに書いています。
replで *print-readably*
だけみると、値については教えてくれますが、変数については教えてくれません。
さっきのしらべものの途中で見つけた http://d.hatena.ne.jp/athos/20111204/elephant_things_in_clojure に #'fact
の表記があったため、試してみたところ
pe-16.core=> #'*print-readably*
#'clojure.core/*print-readably*
どこの変数なのか教えてくれました。期待がもてそうです。(あとで調べてわかったことですが、#'x
は (var x)
と等価のようです。)
話を戻して、たしか meta
とかあった気がしたので打ってみたところ
pe-16.core=> (meta #'*print-readably*)
{:ns #<Namespace clojure.core>, :name *print-readably*, :added "1.0", :doc "When set to logical false, strings and characters will be printed with\n non-alphanumeric characters converted to the appropriate escape sequences.\n\n Defaults to true"}
いろいろ教えてもらえましたが、:dynamic
みたいな属性がありません。
代わりに関数の方には関係のありそうなものが見つかりました。
pe-16.core=> (meta #'pr)
{:ns #<Namespace clojure.core>, :name pr, :arglists ([] [x] [x & more]), :dynamic true, :added "1.0", :doc "Prints the object(s) to the output stream that is the current value\n of *out*. Prints the object(s), separated by spaces if there is\n more than one. By default, pr and prn print in a way that objects\n can be read by the reader", :line 3269, :file "clojure/core.clj"}
pe-16.core=> (meta #'print)
{:ns #<Namespace clojure.core>, :name print, :arglists ([& more]), :added "1.0", :static true, :doc "Prints the object(s) to the output stream that is the current value\n of *out*. print and println produce output for human consumption.", :line 3316, :file "clojure/core.clj"}
pr
の方には :dynamic true
が、print
の方には :static true
がそれぞれあります。もしかしてこれは「挙動がダイナミックスコープの変数に依存している」ことを示す何かなのかもしれないと思ったのですが、先に貼ったprint
系関数のドキュメントを見る限り、どちらも出力先が *out*
に依存するので、関係ないのかもしれません。
ダメ押し(ダメ押され?)に
pe-16.core=> (meta #'pr-str)
{:ns #<Namespace clojure.core>, :name pr-str, :arglists ([& xs]), :added "1.0", :static true, :doc "pr to a string, returning it", :line 4190, :file "clojure/core.clj", :tag java.lang.String}
pe-16.core=> (meta #'print-str)
{:ns #<Namespace clojure.core>, :name print-str, :arglists ([& xs]), :added "1.0", :static true, :doc "print to a string, returning it", :line 4208, :file "clojure/core.clj", :tag java.lang.String}
どちらも :static true
なので、どうやら関係ないのでしょうか?
ちょっと立ち戻って、pr
変数自体がダイナミックスコープなのではと思い当たり、試してみたところ、そのとおりでした。
pe-16.core=> pr
#<core$pr clojure.core$pr@22c190b5>
pe-16.core=> (binding [pr 1] pr)
1
pe-16.core=> pr-str
#<core$pr_str clojure.core$pr_str@1a2bc3>
pe-16.core=> (binding [pr-str 1] pr-str)
IllegalStateException Can't dynamically bind non-dynamic var: clojure.core/pr-str clojure.lang.Var.pushThreadBindings (Var.java:353)
pr
自体を一時的に置き換えるのは……すごくおもしろそうなのですが、ひとまず置いておきます。
*print-readably*
はダイナミックスコープっぽいのに、どうして:dynamic true
がないのでしょう?
その答えなのかどうかはわかりませんが、Clojureのソースツリーを 'print-readably' でgrepしたところ、
どうもこの変数はJavaでセットアップしているらしいことがわかりました。
https://github.com/clojure/clojure/blob/79a1b793f87af417b430450f3c24e7cfe456e3e2/src/jvm/clojure/lang/RT.java#L215
名前からしてsetDynamic
でダイナミックスコープを指定していると予想されます。とすれば、自分でsetDynamic
を呼べば、ダイナミックスコープになるに違いありません。試してみます。
まず、ふつうにdef
した場合はbinding
できません。
pe-16.core=> (def x 1)
#'pe-16.core/x
pe-16.core=> (binding [x 2] x)
IllegalStateException Can't dynamically bind non-dynamic var: pe-16.core/x clojure.lang.Var.pushThreadBindings (Var.java:353)
つぎに、RT.java中のコードを真似て setDynamic
してみると
pe-16.core=> (import clojure.lang.Var clojure.lang.Symbol clojure.lang.Namespace)
clojure.lang.Namespace
pe-16.core=> (def pe-16ns (Namespace/findOrCreate (Symbol/intern "pe-16.core")))
#'pe-16.core/pe-16ns
pe-16.core=> (. (Var/intern pe-16ns (Symbol/intern "x") nil) setDynamic)
#'pe-16.core/x
pe-16.core=> (binding [x 2] x)
2
binding
が使えました。ダイナミックスコープになってます!
……と言いたいところですが
pe-16.core=> x
nil
元のx
の値にアクセスできなくなってしまいました。大きなシステムの中の機能をよくわからないまま単独で叩いているのですから、
挙動が不完全なのは、まあ仕方ありません…?
もしかすると、def
で再定義相当のことをしてしまっているのではないでしょうか?
後でソースをもう一度見てわかったのですが: https://github.com/clojure/clojure/blob/79a1b793f87af417b430450f3c24e7cfe456e3e2/src/jvm/clojure/lang/Var.java#L130 3引数の
Var/intern
は、4引数目のreplaceRoot
にtrue
を指定したのと同じことになるようです。 やはり、これはいかにも束縛を作りなおしてしまっています。nil
になったのは、第3引数のnil
がそのまま初期値になったためですね。
というわけで、replを上げなおして、やり直します。
pe-16.core=> (def x 1)
#'pe-16.core/x
pe-16.core=> x
1
普通に定義したあと、binding
が失敗することを再確認します。
pe-16.core=> (binding [x 2] x)
IllegalStateException Can't dynamically bind non-dynamic var: pe-16.core/x clojure.lang.Var.pushThreadBindings (Var.java:353)
エラーになったからといって、元の値が消えたりはしていません。
pe-16.core=> x
1
さてsetBinding
はもっと簡単に呼べそうな気がしたので、試します。
pe-16.core=> (. #'x setDynamic)
#'pe-16.core/x
少なくともエラーにはなっていません。ダイナミックスコープになっているか試します。
pe-16.core=> (binding [x 2] x)
2
pe-16.core=> x
1
やった!
RT.javaで*print-readably*
を初期化している部分との差異がわかれば謎の解明に近づけそうなので、素潜りしてみることにします。
def
で ^:dynamic
する場合もsetDynamic
しているに違いない……と予想して、setDynamic
でgrepすると、Compiler.java 中にたくさんヒットしました。
それらしい場所はおそらく2箇所だけで、いずれもDefExpr
および DefExpr.Parser
クラス中にあります。名前からして、おそらくパーサの部品で、かつdef
の処理をしている部分に違いありません。
https://github.com/clojure/clojure/blob/79a1b793f87af417b430450f3c24e7cfe456e3e2/src/jvm/clojure/lang/Compiler.java#L412
https://github.com/clojure/clojure/blob/79a1b793f87af417b430450f3c24e7cfe456e3e2/src/jvm/clojure/lang/Compiler.java#L494
これを追うのもすごく興味深いのですが、ふと「setDynamic
があるならisDynamic
もあるのでは?」と思いついたので、それを試してみることにします。
さっきのreplのまま
pe-16.core=> (. #'x isDynamic)
true
元々の目的だった「変数が動的スコープかどうか」が、どうやらこれで分かりそうです!
pe-16.core=> (. #'*print-readably* isDynamic)
true
pe-16.core=> (. #'pr isDynamic)
true
pe-16.core=> (. #'print isDynamic)
false