- LT発表資料です
- Clojure CLIがインストールされていれば、以下で実行できます:
$ clj -Sdeps '{:deps {athos/form-and-env {:git/url "https://gist.github.com/athos/1e92959529dc60316cc2fff4ed3990ff.git" :sha "343f21f5dc5999d9eeef3d993a31755ebd9b84a4"}}}'
$ clj -Sdeps '{:deps {athos/form-and-env {:git/url "https://gist.github.com/athos/1e92959529dc60316cc2fff4ed3990ff.git" :sha "343f21f5dc5999d9eeef3d993a31755ebd9b84a4"}}}'
{:paths ["."] | |
:deps {org.clojure/clojure {:mvn/version "1.10.0"} | |
jise {:mvn/version "0.1.0-SNAPSHOT"}}} |
(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))) | |
) |