関数内だけで使うローカル関数定義は defn ではなく letfn を使う
ローカル関数のつもりで defn をネストさせても defn は動的にvarを作っているだけなので関数外からも見えてしまう。
user> (defn aaa [] (defn bbb [] 1) 1) #'user/aaa user> (aaa) 1 user> (bbb) 1
しかも遅い。
(defn aaa1 [] (defn bbb [] 1) 1) (defn aaa2 [] 1) user> (time (dotimes [_ 10000000] (aaa1))) "Elapsed time: 4083.291 msecs" nil user> (time (dotimes [_ 10000000] (aaa2))) "Elapsed time: 58.34 msecs" nil
ローカル関数は letfn を使用する。
(defn aaa1 [] (letfn [(bbb [] 1)] 1)) user> (time (dotimes [_ 10000000] (aaa1))) "Elapsed time: 100.981 msecs" nil
ちゃんと本に書いてあるのに全然覚えてなかった。
ちなみに letfn を利用して scheme のようにローカル関数を定義するマクロは以下のような感じになった。
(defn envelop-letfn [body] (if (zero? (count (filter #(= (first %) 'define) body))) body `(letfn [~@(map (fn [exp] (rest exp)) (filter #(= (first %) 'define) body))] ~@(filter #(not= (first %) 'define) body)))) (defmacro defun [name & fdecl] (let [m (if (string? (first fdecl)) {:doc (first fdecl)} {}) fdecl (if (string? (first fdecl)) (next fdecl) fdecl) m (if (map? (first fdecl)) (conj m (first fdecl)) m) fdecl (if (map? (first fdecl)) (next fdecl) fdecl) fdecl (if (vector? (first fdecl)) (list fdecl) fdecl) m (if (map? (last fdecl)) (conj m (last fdecl)) m) fdecl (if (map? (last fdecl)) (butlast fdecl) fdecl)] `(defn ~name ~@(map #(list (first %) (envelop-letfn (rest %))) fdecl) ~m)))
define はただのマーカーなので defn でも何でも好きなのに変更できる。
例えばこのように定義して
(defun outer [arg1 arg2] (define inner1 [iarg1 iarg2] (+ iarg1 iarg2)) (define inner2 [iarg1 iarg2] (* iarg1 iarg2)) (inner1 arg1 arg2) (inner2 arg1 arg2))
展開させると
(clojure.core/defn outer ([arg1 arg2] (clojure.core/letfn [(inner1 [iarg1 iarg2] (+ iarg1 iarg2)) (inner2 [iarg1 iarg2] (* iarg1 iarg2))] (inner1 arg1 arg2) (inner2 arg1 arg2))) {})
ちゃんと letfn に展開されました。