関数内だけで使うローカル関数定義は 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 に展開されました。



Thanks to Bill and Kevin.