|
(ns form-and-env |
|
(:require [clojure.pprint :refer [pprint]] |
|
[jise.core :refer [defclass]] |
|
[jise.utils :as jise])) |
|
|
|
(comment |
|
|
|
- 概要 |
|
- &formと&envはマクロの中だけで使える暗黙の引数 (コンパイラがマクロに渡してくれる) |
|
- &form:マクロ呼出しのフォーム全体 |
|
- &env:マクロ呼出しのフォームを含むスコープ内のレキシカル環境 |
|
- ローカル束縛の名前 -> AST (undocumented) のマップ |
|
- ClojureとClojureScriptだと構造が大きく異なる (今回はClojureの話のみ) |
|
- マクロ呼出しのフォームを取り巻く周囲のコードによってマクロ展開結果を変化させたい場合に便利 |
|
- Common Lispでいうところの &whole と &environment (?) |
|
|
|
) |
|
|
|
(defmacro &form-example [& args] |
|
`'~&form) |
|
|
|
(comment |
|
|
|
(&form-example) |
|
;=> (&form-example) |
|
|
|
(&form-example 1 2 3) |
|
;=> (&form-example 1 2 3) |
|
|
|
- &formからはマクロ呼出しのフォームがそのまま取得できる |
|
- このままだと何が嬉しいのか分からない |
|
|
|
) |
|
|
|
(defmacro &form-meta [] |
|
(meta &form)) |
|
|
|
(comment |
|
|
|
(&form-meta) |
|
;=> {:line 38, :column 3} |
|
|
|
^:foo (&form-meta) |
|
;=> {:line 41, :column 3, :foo true} |
|
|
|
^{:my-special-meta 42} |
|
(&form-meta) |
|
;=> {:line 44, :column 3, :my-special-meta 42} |
|
|
|
- マクロ呼出しのフォームについているメタデータも取得できる |
|
- そのフォームが現れる行数・桁数がメタデータとしてついてくる |
|
- ユーザがコードにつけたメタデータも同様に取得できる |
|
|
|
) |
|
|
|
(comment |
|
|
|
- 実用例 |
|
|
|
- 自作ライブラリJiSEではアクセス修飾子や型をメタデータとしてフォームにつけられるようにしている |
|
|
|
^:public |
|
(defclass C |
|
^:private ^int (def x) |
|
^:public ^int |
|
(defm inc [] (inc! x))) |
|
|
|
(def c (C.)) |
|
|
|
(.inc c) ;=> 1 |
|
(.inc c) ;=> 2 |
|
(.inc c) ;=> 3 |
|
|
|
- *file* と合わせて、ソースコードのあるファイルと行が分かるので、マクロ呼出しのフォームのソースコードを取得することもできる |
|
|
|
) |
|
|
|
(defmacro &env-example [] |
|
(pprint &env)) |
|
|
|
(comment |
|
|
|
- &envでマクロ呼出しのフォームを含むスコープ内のレキシカル環境が取得できる |
|
|
|
(&env-example) |
|
|
|
- 周りにローカルな束縛が何もなければnil |
|
|
|
(let [x 42] |
|
(&env-example)) |
|
|
|
- x -> LocalBinding (中身は分からない)のマップが返る |
|
|
|
(defn f [x] |
|
(let [y 42 z "foo"] |
|
(&env-example))) |
|
|
|
- どれだけネストしていてもその時点のスコープで見えている束縛の情報がすべて取得できる |
|
|
|
) |
|
|
|
(defmacro locals [] |
|
(into {} (map (fn [[sym _]] [`'~sym sym])) &env)) |
|
|
|
(comment |
|
|
|
(locals) |
|
;=> {} |
|
|
|
(let [x 42 y "foo"] |
|
(locals)) |
|
;=> {x 42, y "foo"} |
|
|
|
- レキシカル環境を実行時の値として取得できる |
|
|
|
(defmacro object [] |
|
`(let [locals# (locals)] |
|
(fn [field#] |
|
(locals# field#)))) |
|
|
|
(def obj |
|
(let [x 42 y "foo"] |
|
(object))) |
|
|
|
(obj 'x) ;=> 42 |
|
(obj 'y) ;=> "foo" |
|
|
|
- let over lambda的なシンプルなオブジェクトを作ることができる |
|
|
|
) |
|
|
|
(defmacro infer-types [] |
|
(into {} (map (fn [[sym lb]] [`'~sym (.getJavaClass lb)])) &env)) |
|
|
|
(comment |
|
|
|
(let [x 42 y "foo"] |
|
(infer-types)) |
|
;=> {x long, y java.lang.String} |
|
|
|
(defn f [^double x] |
|
(infer-types)) |
|
|
|
(f 42) |
|
;=> {x double} |
|
|
|
- マップの値であるLocalBindingはコンパイラが内部で使うAST |
|
- LocalBindingを使うとその束縛に大してコンパイラが推論した型が取得できる |
|
|
|
) |
|
|
|
(comment |
|
|
|
- 実用例 |
|
|
|
- 自作ライブラリJiSEではDSLからClojureの束縛にアクセスできるようにしている |
|
- JiSEのDSLは静的型つき言語なのでClojureの束縛からも可能な限り型情報を取得したい |
|
- 上の infer-types の要領でClojureの束縛から型を推論してその型情報を利用 |
|
|
|
(defn sum [^long n] |
|
(jise/let [sum 0] |
|
(for [i 0 (< i n) (inc! i)] |
|
(set! sum (+ sum i))) |
|
sum)) |
|
|
|
(sum 10) ;=> 45 |
|
|
|
- 上の定義はだいたい以下のように展開される |
|
|
|
(defn sum [^long n] |
|
(let [sum 0] |
|
((let [obj15181 (new f15175)] |
|
(set! (.-n obj15181) n) |
|
obj15181) |
|
sum))) |
|
|
|
^:public |
|
(defclass f15175 [clojure.lang.IFn] |
|
^:public (def ^long n) |
|
^:public ^Object |
|
(defm invoke [^Object sum] |
|
(let [^long sum sum] |
|
(for [i 0 (< i n) (inc! i)] |
|
(set! sum (+ sum i))) |
|
sum))) |
|
|
|
) |