Wann würde ich ein Makro anstelle einer Funktion benötigen?

7

Ich bin neu in Clojure, ich bin neu in Makros und ich habe keinen vorherigen Hintergrund in Lisp. Ich fuhr fort, mein eigenes Switch-Case-ähnliches Formular zu erstellen, und endete mit folgendem:

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

und dann versucht, eine Funktion zu machen und endete mit diesem:

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

Beide

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

und

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

Funktioniert gut.

Was könnte das Szenario sein, in dem ich ein Makro benötige und eine Funktion nicht funktioniert? Gibt es einen Nachteil in meinen Funktions- oder Makroimplementierungen?

Amogh Talpallikar
quelle
2
Ich habe kein spezielles Beispiel für Sie, aber die einfache Antwort lautet, dass Makros zum Erstellen einer neuen Syntax benötigt werden (wie in domänenspezifischen Sprachen). Wie Ihr Beispiel zeigt, sind gewöhnliche erstklassige Lisp-Funktionen in vielen Fällen bereits leistungsfähig genug, um die Aufgabe ohne Makros zu erledigen. Sie sollten normale Funktionen gegenüber Makros bevorzugen. Verwenden Sie Makros nur, wenn eine normale Funktion nicht ausreicht.
Robert Harvey
1
Siehe auch stackoverflow.com/questions/2561221
Robert Harvey

Antworten:

6

Ein Vorteil von Makros besteht darin, dass sie nicht den gleichen Bewertungsregeln wie Funktionen folgen, da sie nur Transformationen für den Code durchführen.

Ein Beispiel für ein Konstrukt, das Sie nicht nur mit Funktionen erstellen können, ist das Threading- Makro von Clojure .

Sie können einen Ausdruck als erstes Argument in eine Folge von Formularen einfügen:

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

ist äquivalent zu

(* (+ 5 3) 4)

Sie könnten diese Art von Konstrukt nicht nur mit Funktionen erstellen, da vor der ->Auswertung des Ausdrucks die innere (+ 3)und (* 4)die Bedeutung ->erhalten würde

(-> 5
    3
    4)

und es muss die tatsächlichen Funktionen sehen, die verwendet werden, um zu arbeiten.

Überlegen Sie in Ihrem Beispiel, ob das Ergebnis in einigen Fällen Nebenwirkungen haben sollte, oder rufen Sie eine andere Funktion auf. Eine Funktionsversion kann nicht verhindern, dass dieser Code in einer Situation ausgeführt wird, in der dieses Ergebnis nicht ausgewählt wird, wohingegen ein Makro verhindern könnte, dass dieser Code ausgewertet wird, es sei denn, es ist die gewählte Option.

Justin
quelle
1
Ich verstehe es. Ich kann nicht über eine Funktion hinaus denken. Am Ende mache ich immer ein Konstrukt, das wie eine Funktion aussieht und mit einer Funktion erstellt werden kann. weil ich es gewohnt bin, Code zu schreiben, der keine Sprachen und Konstrukte oder DSls erzeugt.
Amogh Talpallikar
2

Ein lustiger Aspekt von Makros ist, dass sie Ihnen die Möglichkeit geben, die Syntax Ihres Lisp zu erweitern und neue syntaktische Funktionen hinzuzufügen. Dies geschieht nur, weil an ein Makro übergebene Argumente nur zur Laufzeit und nicht zum Zeitpunkt von ausgewertet werden Kompilieren Sie Ihr Makro. Als Beispiel -> ->>bewerten die ersten / letzten Thread-Makros in clojure ihre Ausdrucksargumente nicht, andernfalls könnten diese ausgewerteten Ergebnisse nichts mehr akzeptieren (sagen wir (+ 3) => 3und 3ist keine Funktion, die Ihr erstes Hauptargument in so etwas akzeptieren könnte (-> 4 (+ 3))).

Wenn mir diese Syntax gefällt und ich sie meiner Common Lisp-Implementierung hinzufügen möchte (die sie nicht hat), kann ich sie addieren, indem ich selbst ein Makro definiere. Etwas wie das:

;;; 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)))

Jetzt könnte ich sie in Common Lisp genauso verwenden wie in Clojure:

(-> #'+
    (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)

Vielleicht möchten Sie auch eine neue Syntax für die rangeFunktion von clojure mit Ihren eigenen Schlüsselwörtern für Ihre Syntax haben, etwa:

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

kann wie folgt definiert werden:

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

oder fügen Sie etwas hinzu, das Common Lisp ähnelt (nur zum Beispiel, da all dies natürlich bereits in den Sprachen möglich ist!):

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

quelle
-2

Ich bin mir nicht ganz sicher, was Ihre Switch-Case-Funktion tun soll. Was sollte der Wert sein von:

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

Dies kann hilfreich sein, wenn Sie die Quelle der zugehörigen Kernfunktionen / Makros mithilfe von repl angezeigt haben

(clojure.repl/source case)

Im Allgemeinen ist ein Grund, warum Sie möglicherweise ein Makro im Gegensatz zu einer Funktion wünschen, die Verwendung des caseKernmakros

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

wird nicht hallo drucken. Wenn casees jedoch als Funktion definiert wäre, würde "Hallo" auf dem Bildschirm ausgegeben und der gesamte Ausdruck würde ausgewertet "not there". Dies liegt daran, dass Funktionen ihre Argumente vor jedem Aufruf der Funktion auswerten.

WuHoUnited
quelle
3
Dies beantwortet die Hauptfrage nicht wirklich. Es wird nur das Beispiel nicht ausgewählt.
Florian Margaine
1
Der letzte Teil beantwortet die Frage. Es sagt etwas, was ein Makro kann, was eine Funktion nicht kann.
WuHoUnited
Sie hätten etwas klarer darüber sein können, wie sich die Argumentbewertung bei Makros unterscheidet.
Robert Harvey
Ich habe Makros gelernt. Ich wollte ein Konstrukt erstellen, vorausgesetzt, es war nicht da. Also dachte ich daran, einen Schalterfall mit dem if-Formular zu erstellen. Mir fiel nichts anderes ein.
Amogh Talpallikar