2

シンボルのベクトルを作成する次の関数があります。

(defn makevars [n] 
"Gives a list of 'n' unique symbols" 
(let [vc (repeatedly n #(gensym ))] vc)) 

これは問題のあるマクロです:

(defmacro make-strings [syms]
;eg given 2 strings ["abc" "ab"] => ("aa" "ab" "ba" "bb" "ca" "cb")  
(let [nl (count syms) vs (makevars nl) forargs (vec (interleave vs syms))]  
`(for ~forargs (str ~@vs))))

マクロが実行されると(make-strings ["abc" "ab"])、目的の出力が発生します。ただし、マクロを として実行すると(make-strings sy)、sy が次のように定義され、次の["abc" "ab"]エラーが発生します。

UnsupportedOperationException count not supported on this type: Symbol  clojure.lang.RT.countFrom (RT.java:545)

何を間違って実行しましたか? どうすれば修正できますか? マクロロアに慣れていないので、私の理解に何か問題があると思います。

4

3 に答える 3

4

まず、ここにマクロをドロップし、単純な関数を使用して達成したいロジックを実装することを強くお勧めします: マクロは、便利なカスタム構文を作成したい場合にのみ役立ちます。二組のキャラクター。最も簡単な方法は、contrib lib math.combinatoricsを使用することです。

user=> (def sy ["abc" "ab"])
user=> (map #(apply str %) (apply cartesian-product sy))
("aa" "ab" "ba" "bb" "ca" "cb")

そうは言っても、あなたが経験する問題は、マクロがコンパイル時に実行されるため、引数として渡す未評価のフォームを受け取ることです。これは、次のことを意味します。

(make-strings sy)

によって参照されるベクトルを渡す代わりにsy、まさにそのシンボルsyがマクロに与えられるmake-stringsため、呼び出しは

(count sy)

その後、上記の例外が発生します。マクロはコンパイル時の性質のために入力を評価できないため、マクロ内からシンボルの背後にある値を取得することはできません。そのようなシンボルに対するすべての操作は、マクロ自体の返された形式で実行する必要があります。その後、実行時に実行されます。

上記はfor、リテラルまたはシンボルのいずれかを渡すことができるようにしたい場合に、バインディングを作成する戦略を実行不可能にします。

于 2013-01-21T07:45:25.880 に答える
3

skuro は、何がうまくいかないのか、その理由について適切な回答を提供してくれると思います。また、元の目標を解決するために使用できる非常に便利なライブラリも提案してくれます。「では、なぜ私はこれを読んでいるのですか?」私はあなたが尋ねると聞きます。数行のコードだけで、デカルト積に関数を適用する方法を一般化する方法を共有できたら面白いと思いました。

一般化するとは、次のようなことができるようになることを目標としていることを意味します。

user> (permute str ["abc" "ab"])
=> ("aa" "ab" "ba" "bb" "ca" "cb")

これまで存在しなかったこの置換関数を開発した場合の良い点は、同じ関数を使用して次のようなことができることです。

user> (permute + [[1 2 3] [10 20 30]])
=> (11 21 31 12 22 32 13 23 33)

これはおもちゃの例ですが、この方法で一般化することで得られる柔軟性が伝わることを願っています。

さて、ここに私が思いついた非常に簡潔な方法があります:

(defn permute [f [coll & remaining]]
  (if (nil? remaining)
    (map f coll)
    (mapcat #(permute (partial f %) remaining) coll)))

私が始めた中心的なアイデアは、結合するさまざまな文字列と同じ数の反復を反復することでしたmapmapcat私はあなたの例から始めて、「冗長な」非一般的な解決策を書きました:

user> (mapcat (fn [i] (map (partial str i) "ab")) "abc")
=> ("aa" "ab" "ba" "bb" "ca" "cb")

これはmapcat関数 down"abc"です。具体的には、この時点で使用している単一の要素 ( ) と、 の各要素を一緒に歌うmapcat関数ダウン"abc"です。mapstr"abc"i"ab"

これを関数に一般化する方法を理解するために、1 つ「深いレベル」に進み、3 番目の文字列で試してみる必要がありました。

user> (mapcat (fn [i] (mapcat (fn [j] (map (partial str i j) "def")) "ab")) "abc")
=> ("aad" "aae" "aaf" "abd" "abe" "abf" "bad" "bae" "baf" "bbd" "bbe" "bbf" "cad" "cae" "caf" "cbd" "cbe" "cbf")

これは、要素を組み合わせた方法で一緒に歌う関数ですmapcat。ふぅ。今、私はどのように一般化できるかを見始めました。最も内側の式は、再結合する文字列のリストの最後の文字列の下に、常にある種の部分関数を ing することになります。外側の式は、最も外側の文字列が文字列のリストの最初の文字列を使用して再結合するところまで、連続してより前方の文字列を持つ s です。mapcatmapstrmapstrmapcat

strここから、部分関数全体を一度に定義する必要はなく、再帰的に呼び出して「構築」できることに気付いたと思いますpermute

関数がどのように機能するかを説明するのに十分なコンテキストを提供できたことを願っています。の最後の反復permuteは、残りの colls がない場合に発生します (つまり、 を(nil? remaining)返しますtrue)。それmapは、最後の coll で与えられた関数です。

残りの colls がある場合は、現在の coll のバリアントにmapcatなります。この置換バリアントは、無名引数でpermuteの部分関数を使用しており、残りの colls をダウンさせています。これを行うことで、colls のリストの最後に到達すると最終的に呼び出される部分関数が段階的に作成されます。次に、頭の中で、逆方向にトレースし、ネストされたs を呼び出して、結果として再結合された colls で最終的に解き明かされることを想像します。fpermutemapcat

この関数は簡潔ですが、おそらく最適ではないと思います。率直に言って、私は CS のバックグラウンドをあまり持っていませんが、Clojure について読んだことから、自己再帰呼び出しの代わりにloop/を使用recurすると、はるかに「効率的」になる傾向があります。最適化が重要な場合はloop、使用する関数を作り直すのはかなり簡単だと思います。recur

于 2013-01-21T08:55:59.093 に答える
1

マクロ パラメーターは評価されません。マクロにパラメーターとして渡す["abc" "ab"]と、文字列は自己評価オブジェクトであるため、文字列のベクトルとして取得されます。しかし、syマクロを送信すると、シンボルだけが取得されます'sy。そのため、エラーが発生します。

于 2013-01-21T08:27:15.970 に答える