1

このコードを検討してください -

(defn make-getter
  [pred]
  (defn getter
    [db-name htree-name]
    (filter pred (HTreeMap. db-name htree-name))))

(def meta-data-key? #(= (.getKey %) "META_DATA"))
(def not-meta-data-key? #(not (= (.getKey %) "META_DATA")))

(def get-type (make-getter meta-data-key?))
(def get-records (make-getter not-meta-data-key?))

HTreeMap は、JDBM HTree の上に Map インターフェースを実装する Java クラスです。マップには、キーが「META_DATA」であるレコードとそれ以外のレコードの 2 種類があります。get-type 関数が返すのは「META_DATA」キーを持つエントリのみであり、get-records は「META_DATA」キーを持つエントリ以外のすべてを返す必要があります。しかし、get-type が呼び出されると、getKey() != "META_DATA" のレコードも返されます。get-type と get-records の順序を変更すると

(def get-records (make-getter not-meta-data-key?))
(def get-type (make-getter meta-data-key?))

その場合、両方の関数は getKey() == "META_DATA" であるレコードのみを返します。既に定義されている関数の定義が、後で定義された関数によって上書きされるのはなぜですか?

4

2 に答える 2

3

これは、正しい@ffriendの回答への追加です。コメントにはあまり適していない、いくつかの説明が必要だと思います。

(defn f [x] body...)とほぼ同等(def f (fn [x] body...))です。つまり、関数を作成し、それを の値として割り当てますf。コードの主な問題は、defn(like def) が値ではなくシンボルを返すという事実にあります。あれは:

user=> (defn getter
           [db-name htree-name]
           (filter pred (HTreeMap. db-name htree-name)))
; => #'user/getter

(代わりに現在の名前空間が表示されますuser)

したがって、 と の結果的な定義はget-typeget-recordsそれらの値を記号 にします#'user/getter

user=> getter
; => #<user$getter user$getter@f491ac9>
user=> get-type
; => #'user/getter
user=> get-records
; => #'user/getter

それらは関数のエイリアスとして効果的にgetter機能します。

それに加えて、 をgetter呼び出すたびに の値を再定義するmake-getterと、常に最新の定義を取得する理由がわかります。

これを修正する正しい方法は、代わりに次のように定義make-getterすることです。fndefn

(defn make-getter
  [pred]
  (fn
    [db-name htree-name]
    (filter pred (HTreeMap. db-name htree-name))))

または、次のようなマクロを作成することもできますdefgetter(十分にテストされていません)。

(defmacro def-getter
  [name pred]
  `(def ~name (make-getter [~pred])))

そして、次のように使用します。

#user=> (def-getter get-type meta-data-key?)
; => #'user/get-type
#user=> (def-getter get-records not-meta-data-key?)
; => #'user/get-records
于 2013-03-08T09:00:29.333 に答える
2

Clojureドキュメントから:

シンボルの名前と現在の名前空間の値( ns )の名前空間を使用してグローバル変数を作成およびインターンまたは検索します。

したがって、を呼び出すたびにmake-getter、新しいグローバル関数が定義されます。もちろん、現在の名前空間にはそのような名前のグローバル関数が1つしかないため、以前のすべてのバインディングが上書きされます。

実際に必要なのは、関数を定義する代わりに匿名クロージャを返すことです。

(defn make-getter
  [pred]
  (fn
    [db-name htree-name]
    (filter pred (HTreeMap. db-name htree-name))))

このようget-recordsget-typesして、2つの異なるクロージャオブジェクトで初期化されます(同じ無名関数ですが、バインドされた述語が異なります)。

于 2013-03-08T06:40:50.917 に答える