8

間違った用語についてはお詫びします。私はコンピュータ サイエンスについてはまったくの初心者で、ほとんど Clojure しか知りません (ただし、Clojure についてはかなりよく知っていると思います)。

したがって、私はこれについて多くの調査を行っていませんが、Clojure コードを作成するときに、そのデータ構造内から「自分がいるデータ構造の中間バージョン」を参照できると便利な場合があります。 ( a によく似ていますlet)。簡単な例:

=> (self-ish {:a 10
              :b (inc (this :a))
              :c (count (vals this))})
=> {:a 10, :b 11, :c 3}
=> (self-ish ["a" "b" (reduce str this)])
=> ["a" "b" "ab"]
//Works in any nested bits too
=> (self-ish [1 2 3 [4 5 (first this)] 6 [7 [8 (cons (second this) (nth this 3))]]])
=> [1 2 3 [4 5 1] 6 [7 [8 (2 4 5 1)]]]

構造は段階的に構築され、どの段階でも現在の中間構造を として参照できるという考え方ですthis。現在の実装のコードは次のとおりです。

//Random straightforward but helpful definitions
(defn map-entry? [obj]
  (instance? clojure.lang.AMapEntry obj))
(def Map clojure.lang.IPersistentMap)
(def Vector clojure.lang.IPersistentVector)
(def List clojure.lang.IPersistentList)
(def Set clojure.lang.IPersistentSet)

(defn append
  [x coll]
  (if-not coll x
    (condp instance? coll
      Map (if (empty? x) coll
            (assoc coll (first x) (second x)))
      Vector (conj coll x)
      Set (conj coll x)
      List (apply list (concat coll [x]))
      (concat coll [x]))))

(defn build-this
  [acc-stack acc]
  (->> (cons acc acc-stack)
       (drop-while list?)
       (drop-while (every-pred empty? identity))
       (reduce append)))

(defn self-indulge
  [acc-stack acc form]
  ;//Un-comment the following to see it print intermediate stages of processing
  #_(println "this:" (build-this acc-stack acc) "\n  at:" form)
  (append (cond
            (coll? form) (reduce (partial self-indulge (cons acc acc-stack))
                                 (if (map-entry? form) []
                                   (empty form))
                                 form)
            (= (quote this) form) (build-this acc-stack acc)
            :else form)
          acc))

(defmacro self-ish
  [form]
  (self-indulge () nil form))

このappend関数はアイテムをコレクションに追加し、同じタイプのコレクションを返します。このself-indulge関数には、フォームの要素を構築するだけの標準的な reduce のようなアキュムレータがあります。self-indulgeまた、アキュムレータ スタックもあり、繰り返されるたびに長くなります。これのポイントは、他の「より高い」アキュムレータを追跡することです。これによりthis、ローカル部分だけでなく、構造全体になります。マクロはself-ishうまくまとめられていますself-indulge(これは を使用して自分自身を呼び出しているpartialため、マクロ パンツを着用することはできません)。

編集: ユースケースの例 私にとって、このマクロはコードの読みやすさを向上させるためのものであり、真に機能を拡張するものではありません。これが役立つのは、部分的に冗長なデータを含む手書きの構造がある場合です。または、「依存」の方が適切な言葉かもしれません。コードを読んで、データ構造のさまざまな部分が保持されているかを確認するのが簡単になる可能性があります。また、構造の一部でデータ値を変更し、その変更を他の部分に反映させたい場合にも役立ちます。例えば:

=> (self-ish {:favorite-books (list "Crime and Punishment" "Mrs. Dalloway")
              :favorite-things (list* "Ice Cream" "Hammocks" (this :favorite-books)})
=> {:favorite-things ("Ice Cream" "Hammocks" "Crime and Punishment" "Mrs. Dalloway"),
    :favorite-books ("Crime and Punishment" "Mrs. Dalloway")}

また、何らかの関数を使用してオンザフライで導出するのではなく、データに焼き付けたものを含めたい場合にも役立ちます。これらのケースはおそらくはるかにまれであり、データを操作するきれいな関数を使用できる場合に、データを不必要にもつれるのは悪い考えだと思います。

私の主な質問:

  1. これは実際に有用ですか、それともあいまいさや複雑さが大きすぎるのでしょうか? このタイプのマクロを必要としている/使用しているのは私だけではないと思います。ここで他の人の経験は何ですか?このようなものを使用していますか?より良い回避策を見つけましたか? このようなものが Clojure ライブラリにない理由はありますか? それとも、まだ見ていないものがあるのでしょうか?
  2. self-ishand とは対照的に、私が使用できるより良い命名規則はありthisますか? たとえば、thisOOP の意味が多すぎるかもしれませんが、よくわかりません。基本的に Clojure しか知りません。
  3. 私はコンピュータ サイエンスにかなり慣れていません。この種のことに関連するアクセス可能で有益なリソースはありますか?私はそれを匿名の自己参照 (おそらく反射の方が適切な言葉でしょうか?) データ構造と呼んでいますか? 親しみやすく有益なものはまだ見つかりません。
  4. self-ishマクロを書くためのより良い方法はありますか? 上記では、現在のバージョンを含めましたが、もっと簡単な方法があるかもしれないという気持ちを揺るがすことはできません.
  5. 「最も賢明な」実装の詳細について、さまざまな質問があります。

    • Traversal : 幅優先か深さ優先か? デプスファースト、プレオーダー、ポストオーダー、インオーダーのどれですか? 今のところ、深さ優先の事前注文だと思いますが、これは理にかなっていますが、気付いていない欠点があるかもしれません.
    • 順序の問題: (関連する以前の私の質問については、こちらを参照してください{}) (つまり、手書きのマップ) 内で、array-mapまたは使用せずに (8 マップ エントリを超える) 順序を適切に維持することは不可能ですsorted-map。つまり、8 マップ エントリを超える場合、{}使用法は安全でない。たぶん、手書きの順序の代わりに、マクロはアイテムを「理想的な」順序で処理するための派手な魔法を行うことができますか? (array-map ...)それとも、目を楽しませる代わりに、すべてのマップをラップする方が良いでしょう{}か?

      //Showing maps with 9 entries failing
      => (self-ish {:a 1
                    :b (inc (this :a))
                    :c (inc (this :b))
                    :d (inc (this :c))
                    :e (inc (this :d))
                    :f (inc (this :e))
                    :g (inc (this :f))
                    :h (inc (this :g))
                    :i (inc (this :h))})
      => NullPointerException   clojure.lang.Numbers.ops (Numbers.java:942)
      //8 works fine
      => (self-ish {:a 1
                    :b (inc (this :a))
                    :c (inc (this :b))
                    :d (inc (this :c))
                    :e (inc (this :d))
                    :f (inc (this :e))
                    :g (inc (this :f))
                    :h (inc (this :g))})
      => {:h 8, :g 7, :f 6, :e 5, :d 4, :c 3, :b 2, :a 1}
      
    • Serial : 私が書いたように、このマクロは と同様に要素を連続的に処理することで無限再帰を回避しますletが、これは奇妙な動作を引き起こす可能性があります。たとえば、上記の簡単な例では、がそのステップにあるため、(reduce str this)戻ります。代わりに、ある種の無限の遅延シーケンスを作成すると便利な場合がありますか? もしそうなら、それはどのように実装されますか?"ab"this["a" "b"]

    • マップ エントリ: 現在、マップ エントリはベクトルのように扱われますがthis、中間ステップで呼び出される可能性があるため、nil「まだ」値が割り当てられていないキーから値を取得することは完全に可能です。それが、私の最初の簡単な例で、:c最終的に 3 にマップされた理由です。中間に にnil対応するもの:cがあり、それもカウントされたためです。これは修正する必要があると思いますか?
    • マクロ以外のユーティリティ: マクロ コンテキストのすぐ外側で使用するのは簡単self-indulgeですが、これが役立つことはありますか?

読んでくれてありがとう、助けていただければ幸いです:)

4

2 に答える 2

2

このアプローチは、私には少し間違っているように感じますが、その理由はよくわかりません。たぶん、マップの構築が順序に依存しているという考えが好きではない....

これは非常に簡単に作成できるマクロであると言えますが、次のように展開されるものが事実上必要になります。

(let [this {}
      this (assoc this :a 1)
      this (assoc this :b (+ (this :a) 3))]
  this)

したがって、適切なマクロは次のようになります (マップの場合):

(defmacro self-ish [bindings]
  `(let [~'this {}
         ~@(mapcat 
           #(do `(~'this (assoc ~'this ~@%)) )    
           (partition 2 bindings) )]
    ~'this))

(self-ish [:a 1
           :b (+ (this :a) 3)])
=> {:b 4, :a 1}

マップ バインディング フォームは順序付けされていないため、バインディング フォームはベクトルになっていることに注意してください。

このイディオムがどれだけ好きかはまだわかりません。私が好む方法は通常、let 形式で構造体を定義し、中間計算に意味のある名前を付けることです。

(let [meaningful-foo (something)
      meaningful-bar (something-else)]
   {:foo meaningful-foo
    :bar meaningful-bar
    :baz (some-calculation meaningful-foo meaningful-bar)})
于 2012-07-26T03:34:49.347 に答える
2

スキームでは(letrec ...)、構造自体の内部のデータ構造の名前を参照できるようにすることでこれが行われます。したがって、これを行う独自の方法を定義したい場合は、それを実装する方が理にかなっています。Clojureで循環参照を作成することは可能ですか?

letrec の利点の 1 つは、ユーザー指定の名前があることです (マクロがネストされている場合thisはシャドウされます)。

[私はあなたのマクロを理解していないので、タイプに関するコメントを削除するように編集しました。]

[更新] また、clojure セクション 8.5.1 の喜びにおける照応の議論に興味があるかもしれません。

于 2012-07-25T23:47:58.000 に答える