Skip to content

Instantly share code, notes, and snippets.

@athos athos/README.md
Last active May 30, 2019

Embed
What would you like to do?
Shibuya.lisp Lisp meetup #76 LT発表資料

Shibuya.lisp Lisp meetup #76 LT発表資料

  • LT発表資料です
  • Clojure CLIがインストールされていれば、以下で実行できます:
$ 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)))
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.