関数ではなくマクロが必要になるのはいつですか?


7

私はClojureを初めて使用し、Macrosを初めて使用します。Lispの経験はありません。私はフォームのような自分のスイッチケースを作成し続け、これで終わりました:

(defmacro switch-case [v cases default] (if (cases v) (cases v) default )) 

そして、関数を作ってみて、これで終わりました:

(defn fn-switch-case [v cases default] (if (cases v) (cases v) default ))

どちらも

(switch-case 5 {6 "six" 7 "seven"} "not there")

そして

(fn-switch-case 5 {6 "six" 7 "seven"} "not there")

うまくいきます。

マクロが必要で、関数が機能しないシナリオは何ですか?関数やマクロの実装に欠点はありますか?


2
具体的な例はありませんが、簡単な答えは、新しい構文を作成するにはマクロが必要であるということです(ドメイン固有言語の場合など)。しかし、あなたの例が示すように、通常のファーストクラスLisp関数は、多くの場合、マクロを必要とせずに仕事をするのに十分強力です。マクロよりも通常の関数を優先する必要があります。通常の関数が機能しない場合にのみマクロを使用します。
Robert Harvey

回答:


6

マクロの利点の1つは、コードに対して変換を実行するだけなので、関数と同じ評価規則に従わないことです。

関数だけでは作成できなかった構造の例は、Clojureのスレッドマクロです。

これにより、一連のフォームの最初の引数として式を挿入できます。

(-> 5
  (+ 3)
  (* 4))

に相当

(* (+ 5 3) 4)

あなたは前から、関数のみを使用して構築体のこの種を作成することができないであろう->、式が評価されたインナー(+ 3)(* 4)なり、意味->受け取ることになります

(-> 5
    3
    4)

機能するためには、実際に使用されている機能を確認する必要があります。

あなたの例では、いくつかのケースの結果が副作用を持つか、他の関数を呼び出すかどうかを検討してください。関数バージョンは、その結果が選択されない状況でそのコードが実行されるのを防ぐことはできませんが、マクロは、オプションが選択されない限り、そのコードが評価されるのを防ぐことができます。


1
わかった。私の心のベコズ、私は機能を超えて考えることはできません。私は常に関数のように見え、関数で実行できる構造を作ることになります。言語や構成体やDSlsを作らないコードを書くことに慣れているからです。
Amogh Talpallikar 2013年

2

マクロの1つの楽しい側面は、lispの構文を拡張して新しい構文機能を追加する可能性を与えることです。これは、マクロに渡された引数が実行時にのみ評価され、マクロをコンパイルします。例としてのClojureの最初/最後のスレッドマクロは、-> ->>そうでない場合は、これらの評価を行った結果が(と言うより何かを受け入れることができなかった、その発現-引数を評価しない(+ 3) => 33のようなもので、あなたの最初の主要な引数を受け入れることができる機能ではありません(-> 4 (+ 3)))。

さて、この構文が好きで、Common Lisp実装(それがない)に追加したい場合は、マクロを自分で定義して追加できます。このようなもの:

;;; in SBCL

;;; first thread:
(defmacro -> (x &rest more)
  (loop for m in more
     for n = `(,(first m) ,x ,@(rest m)) then `(,(first m) ,n ,@(rest m))
     finally (return n)))

;;; last thread:
(defmacro ->> (x &rest more)
  (loop for m in more
     for n = `(,(first m) ,@(rest m) ,x) then `(,(first m) ,@(rest m) ,n)
     finally (return n)))

これで、Clojureと同じ方法でCommon Lispでそれらを使用できるようになります。

(-> #'+
    (mapcar '(2 3 4) '(1 2 3))) ;; => (3 5 7)

(->> #'>
 (sort '(9 8 3 5 7 2 4))) ;; => (9 8 7 5 4 3 2)

またrange、次のような構文の独自のキーワードを使用して、clojureの関数の新しい構文を作成することもできます。

(from 0 to 10) ;=> (0 1 2 3 4 5 6 7 8 9)

(from 0 to 10 by 0.5) ;;=> (0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0 7.5 8.0 8.5 9.0 9.5)

このマクロのように定義できます:

(defmacro from
  "Just another range!"
  [x y z & more]
  `(when (= '~y '~'to)
     (if '~more (range ~x ~z (second '~more))
         (range ~x ~z))))

またはCommon Lispに似たものを追加します(もちろん、これらはすべて、もちろん言語で実行できるためです)。

(defmacro from (x y z &rest r)
  `(when (eql ',y 'to)
     (if (and ',r (eql (first ',r) 'by))
     (loop for i from ,x to ,z by (second ',r) collect i)
     (loop for i from ,x to ,z collect i))))

(from 0 to 10) ;=> (0 1 2 3 4 5 6 7 8 9 10)
(from 0 to 5 by 1/2) ;=> (0 1/2 1 3/2 2 5/2 3 7/2 4 9/2 5)

-2

あなたはあなたのスイッチケース関数が何をしたいのか正確にはわかりません。何の値にする必要があります:

(def x 5)
(switch-case x {5 :here}
             :not-there)

replを使用して関連するコア関数/マクロのソースを表示した場合に役立つことがあります

(clojure.repl/source case)

しかし、一般的には機能とは対照的に、マクロを望むかもしれない理由は、コア使用していることであるcaseマクロを

(case 6
  5 (print "hello")
  "not there")

こんにちは印刷しません。ただし、case関数として定義されている場合、「hello」が画面に出力され、式全体がと評価され"not there"ます。これは、関数が関数を呼び出す前に関数が引数を評価するためです。


3
これは主な質問には実際には答えません。例では一口だけです。
Florian Margaine、2013年

1
最後の部分は質問に答えます。マクロができることは関数ではできないことを示しています。
WuHoUnited

マクロによって引数の評価がどのように異なるかについては、もう少し明確でした。
ロバートハーベイ

マクロを学んでいました。そこに存在しないと想定して、構成を作成したかったのです。なのでifフォームを使ってスイッチケースを作ろうと思いました。他には何も考えられなかった。
Amogh Talpallikar、2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.