28

私はClojureを学ぶことを考えていますが、c-syntaxベースの(java、php、c#)命令型言語の世界から来ているので、当然のことながら、それは本当に価値がありますか?そのような質問文は非常に主観的で管理が難しい場合がありますが、私が読み続けているClojure(およびより一般的にはlisp)の1つの特定の特性があります。これは、これまでで最も柔軟な言語になると思われるマクロです。

他の主流言語(C ++、PHP、Perl、Python、Groovy / Java、C#、JavaScriptのいずれかを検討してください)で必要とされる、はるかに洗練されていないソリューション/多くの不要な目的のために、Clojureでのマクロ使用の良い例はありますか?抽象化/ハック/など。

4

6 に答える 6

24

マクロは、新しい言語機能を定義するのに非常に便利です。ほとんどの言語では、新しい構文を取得するために言語の新しいリリースを待つ必要があります。Lispでは、コア言語をマクロで拡張し、機能を自分で追加することができます。

たとえば、Clojureには命令型のCスタイルのfor(i=0 ;i<10; i++)ループはありませんが、マクロを使用して簡単に追加できます。

(defmacro for-loop [[sym init check change :as params] & steps]
  (cond
    (not (vector? params)) 
      (throw (Error. "Binding form must be a vector for for-loop"))
    (not= 4 (count params)) 
      (throw (Error. "Binding form must have exactly 4 arguments in for-loop"))
    :default
      `(loop [~sym ~init value# nil]
         (if ~check
           (let [new-value# (do ~@steps)]
             (recur ~change new-value#))
           value#))))

次のように使用します。

(for-loop [i 0, (< i 10), (inc i)]
  (println i))

関数型言語に命令型ループを追加するのが良い考えであるかどうかは、おそらくここで避けるべき議論です:-)

于 2012-05-03T16:04:34.110 に答える
23

clojureのベースには、あなたが考えていないマクロがたくさんあります...これは良いマクロの兆候です。それらはあなたが人生を楽にする方法で言語を拡張することを可能にします。マクロがなければ、人生はそれほどエキサイティングではありません。たとえば、私たちが持っていなかった場合

(with-out-str (somebody else's code that prints to screen))

次に、アクセスできない可能性のある方法でコードを変更する必要があります。

もう1つの素晴らしい例は

(with-open-file [fh (open-a-file-code ...)]
   (do (stuff assured that the file won't leak)))

マクロのパターン全体with-something-doが、clojureエコシステムに実際に追加されました。


ことわざのマクロコインの反対側は、私が(現在の)プロのClojureの時間のすべてを、非常にマクロの重いライブラリを使用して費やしていることです。ファーストクラスmapこのライブラリの作成者は、次のバージョンで非常に長い時間をかけて、マクロを経由せずにすべての機能を利用できるようにし、私のような人々がやのような高階関数でそれらを使用できるようにしreduceます。

マクロは、生活を楽にするときに世界を改善します。それらがライブラリへの唯一のインターフェースである場合、それらは逆の効果をもたらす可能性があります。マクロをインターフェースとして使用しないでください

一般に、データの形状を正確に把握することは困難です。ライブラリの作成者として、ライブラリの使用を想定する方法に合わせてデータを適切に構造化した場合、ユーザーがライブラリを新しい想像を絶する方法で使用できるように再構造化する方法がある可能性があります。この場合、問題の素晴らしいライブラリの構造は非常に優れていて、作者が意図していなかったことを可能にしました。残念ながら、優れたライブラリは、インターフェイスが関数のセットではなくマクロのセットであったため、制限されていました。ライブラリはマクロよりも優れていたので、彼らはそれを抑制しました。これは、マクロが何らかの原因であると言っているのではなく、プログラミングが難しく、多くの効果をもたらす可能性のある別のツールであり、すべての要素を一緒に使用してうまく機能させる必要があります。

于 2012-05-03T16:25:59.130 に答える
14

私が時々使用するマクロのより難解なユースケースもあります。完全に最適化された簡潔で読みやすいコードを書くことです。簡単な例を次に示します。

(defmacro str* [& ss] (apply str (map eval ss)))

これは、コンパイル時に文字列を連結します(もちろん、コンパイル時定数である必要があります)。Clojureの通常の文字列連結関数はstr、タイトループコードのどこにでも長い文字列があり、いくつかの文字列リテラルに分割したい場合は、スターを追加してstr実行時の連結をコンパイル時に変更するだけです。使用法:

(str* "I want to write some very lenghty string, most often it will be a complex"
      " SQL query. I'd hate if it meant allocating the string all over every time"
      " this is executed.")

もう1つの、ささいな例:

(defmacro jprint [& xs] `(doto *out* ~@(for [x xs] `(.append ~x))))

可変数の&引数(varargs、可変個引数関数)を受け入れることを意味します。Clojureでは、可変個引数関数呼び出しは、ヒープに割り当てられたコレクションを使用して引数を転送します(Javaのように、配列を使用します)。これはあまり最適ではありませんが、上記のようなマクロを使用すると、関数呼び出しはありません。私はそれを次のように使用します:

(jprint \" (json-escape item) \")

PrintWriter.appendこれは、 (基本的に展開されたループ)の3つの呼び出しにコンパイルされます。

最後に、さらに根本的に異なるものをお見せしたいと思います。マクロを使用して、同様の機能のクラスを定義し、大量の定型文を排除するのに役立てることができます。このおなじみの例を見てください。HTTPクライアントライブラリでは、HTTPメソッドごとに個別の関数が必要です。4つのオーバーロードされたシグニチャがあるため、すべての関数定義は非常に複雑です。また、各関数にはApache HttpClientライブラリからの異なるリクエストクラスが含まれますが、それ以外はすべてすべてのHTTPメソッドでまったく同じです。これを処理するために必要なコードの量を見てください。

(defmacro- def-http-method [name]
  `(defn ~name
     ([~'url ~'headers ~'opts ~'body]
        (handle (~(symbol (str "Http" (s/capitalize name) ".")) ~'url) ~'headers ~'opts ~'body))
     ([~'url ~'headers ~'opts] (~name ~'url ~'headers ~'opts nil))
     ([~'url ~'headers] (~name ~'url ~'headers nil nil))
     ([~'url] (~name ~'url nil nil nil))))

(doseq [m ['GET 'POST 'PUT 'DELETE 'OPTIONS 'HEAD]]
  (eval `(def-http-method ~m)))
于 2012-05-04T08:15:37.610 に答える
7

私が取り組んでいたプロジェクトの実際のコード-ボックス化されていないintを使用してネストされたfor-esqueループが必要でした。

(defmacro dofor
  [[i begin end step & rest] & body]
  (when step
    `(let [end# (long ~end)
           step# (long ~step)
           comp# (if (< step# 0)
                   >
                   <)]
       (loop [~i ~begin]
         (when (comp# ~i end#)
           ~@(if rest
               `((dofor ~rest ~@body))
               body)
           (recur (unchecked-add ~i step#)))))))

のように使用

(dofor [i 2 6 2
        j i 6 1]
  (println i j))

どちらが印刷されますか

2 2
2 3
2 4
2 5
4 4
4 5

loopそれは私が最初に手で書いていたraw / sに非常に近いものにコンパイルさrecurれるので、同等のものとは異なり、基本的に実行時のパフォーマンスのペナルティはありません

(doseq [i (range 2 6 2)
        j (range i 6 1)]
  (println i j))

結果として得られるコードは、同等のJavaと比べてかなり有利だと思います。

for (int i = 2; i < 6; i+=2) {
    for (int j = i; j < 6; j++) {
        System.out.println(i+" "+j);
    }
}
于 2012-05-09T01:40:09.460 に答える
6

マクロなしで再作成するのがかなり難しい便利なマクロの簡単な例はですdoto。最初の引数を評価してから、次の形式を評価し、評価の結果を最初の引数として挿入します。これはあまり聞こえないかもしれませんが...

これdotoで:

(let [tmpObject (produceObject)]
 (do
   (.setBackground tmpObject GREEN)
   (.setThis tmpObject foo)
   (.setThat tmpObject bar)
   (.outputTo tmpObject objectSink)))

それになる:

(doto (produceObject)
   (.setBackground GREEN)
   (.setThis foo)
   (.setThat bar)
   (.outputTo objectSink))

重要なのは、それdotoが魔法ではないということです。言語の標準機能を使用して、自分で(再)構築することができます。

于 2012-05-03T18:17:28.543 に答える
3

マクロはClojureの一部ですが、IMHOは、マクロがClojureを学ぶべきまたはすべきでない理由であるとは考えていません。データの不変性、並行状態を処理するための優れた構造、およびそれがJVM言語であり、Javaコードを利用できるという事実が3つの理由です。Clojureを学ぶ他の理由が見つからない場合は、関数型プログラミング言語が、どの言語でも問題に取り組む方法にプラスの影響を与える可能性があるという事実を考慮してください。

マクロを見るには、Clojureのスレッドマクロから始めることをお勧めします。それぞれ、 thread-first->とthread-lastです。このページ、およびClojureについて説明しているさまざまなブログ->>の多くにアクセスしてください。

頑張って楽しんでね。

于 2012-05-03T15:49:43.463 に答える