web-dev-qa-db-ja.com

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

私は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")

うまくいきます。

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

7

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

関数を使用しただけでは作成できなかった構造の例は、Clojureの threading マクロです。

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

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

に相当

(* (+ 5 3) 4)

->式が評価される前は、内側の(+ 3)および(* 4)->を意味するため、関数のみを使用してこの種の構成を作成することはできません。受け取る

(-> 5
    3
    4)

そしてそれは働くために使用されている実際の機能を見る必要があります。

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

6
Justin

マクロの1つの楽しい側面は、LISPの構文を拡張して新しい構文機能を追加する可能性を提供することです。これは、マクロに渡された引数が実行時にのみ評価され、マクロをコンパイルします。例として、clojure -> ->>の最初/最後のスレッドマクロはそれらの式引数を評価しません。そうでない場合、これらの評価結果はこれ以上何も受け入れません(たとえば、(+ 3) => 3および3(-> 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)

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

(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
user230703