読者です 読者をやめる 読者になる 読者になる

(define -ayalog '())

括弧に魅せられて道を外した名前のないプログラマ

暗黙の引数 &form を使って既に束縛がある場合には何もしない defn を定義してみる。

Clojure

昨日まで読んでいた Mastering Clojure Macros の中で &env と &form に触れているところがあった。まぁ正直何に使ったらいいのか分からなかったし、 &env と &form を参照できたとして「で?」である。

これらが何の為にあるのか、というのはどうもこの辺を読むと将来の Clojure(CinC) のために Rich 御大が突っ込んだぽい?

> failed at attempts to figuring it out.. Can somebody tell me what the
> val part contains and how one could use it?

It contains a LocalBindings object which the compiler uses internally to
keep track of a local binding. Note that unlike the keys, the values of
&env are not a stable API, they're implementation details and may well
change. When he added &env I think Rich said he'd look at giving the
values of &env a proper API as part of the future Clojure in Clojure
compiler.

https://groups.google.com/forum/#!topic/clojure/2ddR9fAZEAk

それはともかくとして、今回はこのあまり使い道のない &form を使ってもう少し現実的なことをしてみたいと思います。
この暗黙の引数 &form ですが、メタデータを持っているのでテスティングフレームワークなどの行数を出す仕組みの実装に使われることがあるようです。
Jay Fields' Thoughts: Clojure: &env and &form
つまり、メタデータがある、だからメタデータを参照してその中身によって条件分岐させることが出来たりも当然するわけです(?)。

というわけで「そのシンボルが束縛されているかを確認して、束縛されているなら新たに束縛しない」ような defn を定義してみます。
こんな感じで使えたら良さそうです。

(defn' foo [a b]
  (println a b)) ;=> #'user/foo
(foo 1 2) ;=> 1 2

(defn' foo [a b c]
  (println a b c)) ;=> nil
(foo 1 2 3) ;=> ArityException Wrong number of args (3) passed to: core/eval15574/foo--15575  clojure.lang.AFn.throwArity (AFn.java:429)

みたいな。

これを実現するコードはこう書けます。

(defmacro def' [name any]
  (let [m (meta &form)]
    (if (:need? m)
      (if-not (resolve name) `(def ~name ~any))
      `(def ~name ~any))))

(def
  defn'
  (fn [&form &env name & fdecl]
    `(do (meta ^:need?
               (def' ~name (fn ~@fdecl)))
         #'~name)))

(.setMacro #'defn')

ちょっと僕の Clojure 力低くて定義が雑だけど、だいたいこんな感じで出来ました。そんなに複雑なことはしていないので、簡単に読めると思います。
誰得だけど、どうしても使い道というとこのくらいしかないのかなーという感じでした。あとはもうコンパイラ側で適当に使われるくらいでしょう。