Eine sanfte Einführung in Compile-Time Computing - Teil 3 - wissenschaftliche Einheiten

Es gibt eine ständige Debatte darüber, ob Einheiten in Variablen- und Feldnamen eingeschlossen werden sollen, wie z.

Wir können es besser machen, viel besser. Was wir wollen, ist eine Methode, bei der Zahlen aus Quellcode (Literale) und aus Dateien oder Streams deklariert werden, und das System konvertiert sie nur dann, wenn dies erforderlich ist.

Was wir also * nicht * tun, ist, die Einheit zur Laufzeit wie folgt zu schleppen:

struct double-ish {
   doppelte Anzahl;
   Aufzählungseinheit;
}

… Und dann das Gerät bei jeder Operation zur Laufzeit prüfen und ggf. konvertieren.

Und wir wollen eine inkrementelle Entwicklung. Wir möchten etwas Nützliches * heute *, ohne das Ninja-Subsystem implementieren zu müssen, bevor wir unseren Produktionscode schreiben können.

Erste Ordnung des Geschäfts:

  • Schreiben Sie genug Code, damit Sie die Einheit * heute * aufschreiben können, wenn Sie Literale in den Quellcode eingeben, auch wenn Sie heute keine Zeit haben, etwas Kluges zu tun. Aber jedes Mal, wenn Sie etwas in den Quellcode schreiben, möchten Sie das Gerät * heute * einfügen. Damit Sie später etwas Geschickliches mit dem Einheitsmechanismus machen können, ohne * alle * Ihrer * zuvor eingegebenen Nummern bearbeiten zu müssen.
  • Sieht so aus:

    (defconstant + c + (Einheit 299792458 m / s))
    (defconstant + h + (Einheit 6.62607015e-34 Js))
    (defconstant + e + (Einheit 2.7182882845: keine))
    (defconstant + up-Quark-Masse + (Einheit 2,3 MeV / c ^ 2 +0,7 - 0,5)
    [...]

    Das sieht nach einer guten Möglichkeit aus, die Dinge heute aufzuschreiben. Wie Sie sehen, schreibe ich beim Nachschlagen in Wikipedia auch zusätzliche Metadaten auf, in diesem Fall die Varianz. Es ist auch nicht so, dass ich heute etwas gegen die Varianz tun kann. Aber die Daten sind genau da und ich möchte sie * heute * eingeben.

    Um die Dinge heute in Gang zu bringen, nehmen wir die einfachste Implementierung vor, die es uns ermöglicht, die oben genannten Schritte durchzuführen:

    (defmacro unit (werteinheit & rest mehr)
      (deklarieren (Einheit ignorieren mehr))
      Wert)

    Das macht folgendes:

    • erfordert, dass eine Einheit angegeben wird
      • bereitet weitere Metadaten vor, mit denen wir in einer zukünftigen Version des Makros etwas anfangen werden. Im Moment akzeptieren wir einfach mehr Argumente und werfen sie weg
      • Dieses Makro lässt die oben genannten Defconstants funktionieren. Wir können heute alle Zahlen mit Einheiten eingeben.

        Der nächste Schritt in der Einfachheit besteht darin, alles als SI-Einheiten zu zwingen:

        (defmacro unit (werteinheit & rest mehr)
          (deklarieren (mehr ignorieren))
          (Falleinheit
            (Frau)
            (Js)
            (:keiner)
            (t (Fehler "unbekannte Einheit ~ a" Einheit)))
          Wert)

        Stellen Sie sicher, dass Sie wissen, dass dies zur Kompilierzeit ausgeführt wird. Sie können es verwenden, um diese * Konstanten * zu definieren, und diese sind, soweit der Compiler betroffen ist, Literalkonstanten. Die obige Verarbeitung wird zur Kompilierzeit ausgeführt. Dies bedeutet, dass der Compiler Assemblycode für alle Funktionen kompiliert, die diese Konstanten verwenden. Nichts zur Laufzeit. Zur Laufzeit haben Sie nur die Nummer. Sie lehnen nur Einheiten ab, die Sie nicht kennen.

        Das Schreiben dieses Codes hat weniger als 20 Sekunden gedauert. In Anbetracht der Bearbeitungszeit von Lisp kann ich diesen Code testen, indem ich sowohl die Makro- als auch die Konstantendeklarationen in weniger als 1 Sekunde kompiliere.

        OK, damit frisst sich diese Konstanten auf:

        (defconstant + c + (Einheit 299792458 m / s))
        (defconstant + h + (Einheit 6.62607015e-34 Js))
        (defconstant + e + (Einheit 2.7182882845: keine))

        Aber es gefällt ihm nicht, was nicht genau SI ist:

        (defconstant + up-Quark-Masse + (Einheit 2,3 MeV / c ^ 2 +0,7 - 0,5)

        Die Fehlermeldung ist sehr klar und erscheint um * compile *:

        unbekannte Einheit MEV / C ^ 2
           [Bedingung vom Typ SIMPLE-ERROR]

        Also, was machen wir? Fügen wir einfach MeV / c² in das Makro ein? Nein, wir hatten (vorerst) entschieden, dass unser System intern mit SI-Einheiten arbeitet. Es ist also an der Zeit, diesem Ding auch das Konvertieren beizubringen. Wir ändern auch die Art und Weise, in der der Wert zurückgegeben wird, um ein wenig funktionaler zu sein:

        (defmacro unit (werteinheit & rest mehr)
          (deklarieren (mehr ignorieren))
          (Falleinheit
            (m / s Wert)
            (Js-Wert)
            (: kein Wert)
            (MeV / c ^ 2 (* Wert 1.78266191e-30))
            (t (Fehler "unbekannte Einheit ~ a" Einheit))))

        Dadurch wird die Quarkmasse konstant verarbeitet. Das Ergebnis sieht richtig aus:

        Ja Meister? CL-USER> + Quark-Masse +
        4.100122e-30

        Dies ist wieder zur Kompilierzeit. Lass uns das überprüfen.

        (defun testfun ()
          (+ + Quark-Masse + + C +))
        ;; in SBCL kompilieren, dann zerlegen:
        Ja Meister? CL-USER> (Testfun demontieren)
                                                                                                                                                                                                                          
        ; Demontage für TESTFUN
        ; Größe: 13 Bytes. Herkunft: # x52E3B9B6
        ; B6: 488B15B3FFFFFF MOV RDX, [RIP-77]; Einstiegspunkt für das Parsen ohne Argumente
                                                       ; 2.9979245e8
        ; BD: 488BE5 MOV RSP, RBP
        ; C0: F8-CLC
        ; C1: 5D-POP-RBP
        ; C2: C3 RET
        NULL
        Ja Meister? CL-USER>

        Oh schau. Der Compiler hat dabei nicht nur nur die Zahlen zur Laufzeit belassen, sondern auch erkannt, dass Konstanten arithmetisch sind, und sie zur Compilierzeit gefaltet. Dies wird als konstante Faltung bezeichnet. Konstante Faltung ist in der Regel auch in C ++ und Freunden verfügbar. Das Problem ist, dass Sie eine gute Methode benötigen, um alle Konstanten von Anfang an zu ermitteln, ohne dieselben Daten wiederholt einzugeben und sie bei der Geräteüberprüfung einzugeben.

        Wie lange habe ich gebraucht, um diesen Code zu schreiben? Nun, der größte Schritt bestand darin, die Einheiten in Google einzugeben, um herauszufinden, was die Konstante für die Konvertierung ist. Glücklicherweise ist die Google-Suchmaschine so ausgereift, dass sie über so exotische Einheiten wie MeV / c² Bescheid weiß. Also insgesamt weniger als eine Minute. Zum Testen des neuen Codes musste er kompiliert und ausgeführt werden. Dank der üblichen Lisp-Trendwende dauerte es weniger als eine Sekunde. Weitere Informationen finden Sie hier: https://hackernoon.com/software-development-at-1-hz-5530bb58fc0e

        OK, das ist nützlich. Sie können einfach neue Einheiten, die Sie benötigen, in das Makro einfügen, wenn bei der Eingabe von Werten neue Einheiten angezeigt werden. Der Aufwand ist begrenzt. Sie müssen sich nie für einen Abend hinsetzen und dieses Makro für längere Zeit erweitern.

        Hier gibt es zwei Verbesserungen:

        • Anstatt jede kombinierte Einheit (z. B. m / s) in das Makro einzufügen, können Sie zur Dekompilierung die Deklaration der Einheiten als String ausführen und sie in die grundlegenden, nicht kombinierten Einheiten auflösen (z. B. in Meter analysieren) / Operator und Sekunden). Das ist wie eine Arbeit, die einen Evaluator schreibt. Dies würde die Sicherheit erhöhen, da dadurch die Anzahl der Konvertierungskonstanten reduziert wird, die Sie in das Makro eingeben müssen. Ich möchte zuerst etwas anderes machen.
          • SI-Einheiten saugen irgendwie. Nicht so schwer wie imperiale Einheiten, aber trotzdem. Wenn ich sowieso mit Quarks rumgespielt habe, kann ich Planck-Einheiten intern verwenden. Sie bieten eine Reihe von Vorteilen, unter anderem, dass Sie nicht alle diese Gleitkommawerte mit fester Genauigkeit in Variablen eingeben müssen. Planck-Einheiten sind robust gegen "Änderungen" in z. c die Lichtgeschwindigkeit. In Planck-Einheiten ist c ein grundlegender Baustein mit einem Wert von 1,0. Nifty, wie?
          • OK, also Planck-Einheiten.

            Müssen wir jetzt unsere Konstanten erneut eingeben? Wenn wir 300 Werte im gesamten Quellcode geschrieben und unsere Meinung zu den internen Einheiten unseres Gesamtsystems geändert hätten, müssen wir dann diese 300 Teile im Quellcode ändern?

            Nein, dumm.

            In diesem ganzen Theater geht es darum, * nicht * das tun zu müssen. Solange alle Quellcode-Stellen mit Literalen die richtige Deklaration (unit…) haben, müssen wir nur das Makro ändern. Nach dem Neukompilieren des gesamten Systems mit dem geänderten Makro * werden alle * internen Zustände als Planck-Einheiten definiert - obwohl * keine * Konstanten als Planck-Einheiten eingegeben wurden.

            Aus diesem Grund möchten Sie die Zeitberechnung kompilieren. Unabhängig von den Einheiten, die Sie persönlich bevorzugen, sollten Sie immer Zahlen in die Einheit eingeben, in der Sie sie finden. Wenn Wikipedia Ihnen h in Joule * Sekunden gibt, möchten Sie es als Joule * Sekunden in den Quellcode eingeben. Bei der Eingabe dieser Zahlen möchten Sie nicht mit einem Taschenrechner herumspielen. Die Fehlerrate kann nur steigen.

            OK, so geht es hier:

            (defmacro unit (werteinheit & rest mehr)
              (deklarieren (mehr ignorieren))
              (Falleinheit
                (m / s (/ Wert 2.99792458e + 8))
                (Js (/ Wert 1,054571800e-34))
                (m (/ Wert 1,616229e-35))
                (s (/ Wert 5.39116e-44))
                (kg (/ Wert 2,176470e-8))
                (: kein Wert)
                ;; Dies gibt einen Fehler:
                (MeV / c ^ 2 (Einheit (* Wert 1.78266191e-30) kg))

            So weit so gut, aber dieser verschluckt sich und wir kommen jetzt in den Bereich, in dem wir lernen müssen, wie Lisp-Makros funktionieren. Der folgende Fehler wird ärgerlich erscheinen, aber im nächsten Abschnitt dieses Dokuments werde ich zeigen, wie der ordnungsgemäß verursachte Fehler für weitere Tricks verwendet werden kann.

            OK, der Fehler lautet also (zur Kompilierzeit):

            Der Wert
                   (* WERT 1,78666618-30)
                 ist nicht vom Typ
                   NUMMER
                 beim Binden von SB-KERNEL :: X
            

            Also was ist das? Wenn Sie die Fehlermeldung sorgfältig lesen, werden Sie feststellen, dass der Ausdruck (* Wert 1.7) zu dem Zeitpunkt, zu dem wir ihn im (jetzt rekursiven) Makro verwenden, noch nicht in eine Zahl aufgelöst wurde. Die Bewertung hier hat sich verzögert. Das funktioniert aber nicht. Der Grund, warum dies nicht funktioniert, ist:

            • Wir haben den Compiler gebeten, die eigentliche Arithmetik zur Kompilierzeit auszuführen
              • Was wir jedoch an den arithmetischen Operator übergeben, ist eine Zahl und ein Stück Code. Da wir jetzt die Mathematik gefragt haben, kann es nicht
              • Dies ist nicht der Ort, um alles über die Bewertung von Makro-Argumenten zu lernen. Es kann jetzt damit behoben werden:

                (MeV / c ^ 2 (* Wert (Einheit 1.78266191e-30 kg)))

                Arithmetik mit Unit-Prüfung zur Kompilierzeit.

                Lassen Sie uns zusammenfassend auf diesen nervigen Fehler eingehen, der eine Problemumgehung erfordert, und warum der Mechanismus hinter dem Fehler meiner Meinung nach eine gute Sache ist:

                Diese Bomben:

                (MeV / c ^ 2 (Einheit (* Wert 1.78266191e-30) kg))

                weil (unit…) ein Makroaufruf ist und (MeV / c² (unit (* Wert 1.78266191e-30) kg)) nicht ausgewertet wird, kann der mathematische Operator (*…), den wir zur Kompilierzeit aufrufen, nicht funktionieren . Es übergibt das gesamte Codefragment. Sie können dieses Codefragment durchlaufen.

                OK, lass uns damit spielen, um zu verstehen:

                ;; zu definieren
                (defmacro Unit-Test ...)
                (defun testfun2 ()
                  (Unit-Test 1 kg)

                Der Makro-Unit-Test wird zur Kompilierzeit aufgerufen. Das heißt, wenn Sie die Funktion Unit-Test kompilieren, werden Aktionen im Makro ausgeführt. Wir werden dies zum Debuggen verwenden, indem wir die einzige echte Debug-Funktion verwenden, die in den World-Print-Anweisungen existiert. Oder im Falle von Lisp Formatanweisungen.

                (defmacro unit-test (werteinheit & rest mehr)
                  (deklarieren (mehr ignorieren))
                  (Format t "~% Wert ist '~ a' ~%" Wert)
                  (Format t "Einheit ist" ~ a "~%" Einheit)
                  Wert)
                (defun testfun2 ()
                  (Unit-Test 1 kg)

                Gibt Sie zur Kompilierzeit in dasselbe stdout wie der Compiler:

                Wert ist '1'
                Einheit ist "KG"

                Bevor ich weiter gehe, möchte ich sicherstellen, dass Sie das bekommen.

                SIE HABEN DIE GESAMTE SPRACHE BEI ​​IHRER ENTSORGUNG ZUR COMPILE-ZEIT !!!

                Die ganze Sache Sie können printf / format-Anweisungen zur Kompilierzeit verwenden, um zufällige Datenstrukturen zu drucken, die in Ihrem halbkompilierten Code herumliegen, um sie zu debuggen. In letzter Zeit eine solche Einrichtung mit Vorlagen gesehen?

                Wie auch immer, also schauen wir uns an, was mit dieser Bewertungssache passiert:

                (defmacro unit-test (werteinheit & rest mehr)
                  (deklarieren (mehr ignorieren))
                  (Format t "~% Wert ist '~ a' ~%" Wert)
                  (Format t "Einheit ist" ~ a "~%" Einheit)
                  Wert)
                (defun testfun3 ()
                  (Unit-Test (* 1 1) kg))

                Ausgabe ist:

                ; kompilieren (DEFUN TESTFUN3 ...)
                Wert ist '(* 1 1)'
                Einheit ist "KG"

                Hoppla. Hier hast du es. (* 1 1) wird nicht als ausgewertete Zahl übergeben, sondern als Codefragment. Das ist fantastisch. Ich meine nicht immer, z. nicht am Freitagabend, wenn dies in tief verschachtelten Makros geschieht, und Sie müssen es debuggen.

                Aber es zeigt einen sehr mächtigen Mechanismus:

                (defmacro unit-test2 (Werteinheit & Rest mehr)
                  (deklarieren (mehr ignorieren))
                  (Format t "~% Wert ist '~ a' ~%" Wert)
                  (Format t "Einheit ist" ~ a "~%" Einheit)
                  (wann (Listenwert)
                    (dolist (element wert)
                      (Format t "Listenelement ist '~ a' ~%" Element))))
                  Wert)
                (defun testfun3 ()
                  (Unit-Test2 (* 1 1) kg))

                Ausgabe ist:

                Wert ist '(* 1 1)'
                Einheit ist "KG"
                Listenelement ist '*'
                Listenelement ist '1'
                Listenelement ist '1'

                Woah Wir können diesen Code gehen. Wir haben nicht nur Turing-vollständigen Code, sondern auch Code, der THE ENTIRE LANGUAGE zur Kompilierzeit verwenden kann.

                (defmacro unit-test3 (Werteinheit & Rest mehr)
                  (deklarieren (mehr ignorieren))
                  (Format t "~% Wert ist '~ a' ~%" Wert)
                  (Format t "Einheit ist" ~ a "~%" Einheit)
                  (wann (Listenwert)
                    (dolist (element wert)
                      (if (und (numberp Element) (= Element 42))
                          (Format t "Sieht aus wie die Antwort auf alles ~%")
                          (Format t "Listenelement ist '~ a' ~%" Element))))
                  Wert)
                (defun testfun3 ()
                  (Unit-Test3 (* 42 1) kg))

                Ausgabe:

                ; kompilieren (DEFUN TESTFUN3 ...)
                Wert ist '(* 42 1)'
                Einheit ist "KG"
                Listenelement ist '*'
                Sieht aus wie die Antwort auf alles
                Listenelement ist '1'

                Wir können tun, was wir wollen.

                Es ist nicht auf die Überprüfung des Codes beschränkt. Makros sind dazu da, neuen Code zu erstellen. Also versuchen wir das mal.

                (defmacro unit-test4 (Werteinheit & Rest mehr)
                  (deklarieren (mehr ignorieren))
                  (wann (Listenwert)
                    (dolist (element wert)
                      (wann (und (numberp Element) (= Element 42))
                        (return-from unit-test4 `(progn
                                                   (dotimes (i 4)
                                                     (Format t "Hallo, Welt ~%"))
                                                   ,Wert)))))
                  Wert)
                (defun testfun4a ()
                  (Unit-Test4 (* 41 1) kg))
                (defun testfun4b ()
                  (Unit-Test4 (* 42 1) kg))

                Ausführen es:

                Ja Meister? CL-USER> (testfun4a)
                41
                Ja Meister? CL-USER> (testfun4b)
                Hallo Welt
                Hallo Welt
                Hallo Welt
                Hallo Welt
                42
                Ja Meister? CL-USER>

                Woah Wir haben tatsächlich neuen Code in die Funktion eingefügt. Können wir sehen, was los ist? Sicher:

                Ja Meister? CL-USER> (Macroexpand '(Unit-Test4 (* 41 1) kg))
                (* 41 1)
                T
                Ja Meister? CL-USER> (Macroexpand '(Unit-Test4 (* 42 1) kg))
                (PROGN (DOTIMES (I 4) (FORMAT T "Hallo, Welt ~%")) (* 42 1))
                T
                Ja Meister? CL-USER>

                Dies ist das grundlegendste Makroexpansion-Debugging, das es gibt. In den IDEs gibt es weitaus komplexere Funktionen zum Debuggen von Makroexpandern, z. SCHLEIM. Fein kontrollierte Auswertung, bis Sie herausfinden können, was los ist.

                Nun, wie nutzen wir das zu unserem Vorteil?

                Nun, wir können mit der Unit-Prüfung zur Kompilierzeit rechnen.

                (defmacro plus-with-units (val1 val2)
                  ;; fancy code hier
                  (+ val1 val2))
                ;; das sollte funktionieren
                (defun testfun5a ()
                  (Pluseinheiten mit Einheiten (Einheit 5 m / s) (Einheit 6 m / s)))
                ;; das sollte * nicht * funktionieren
                (defun testfun5b ()
                  (Plus-mit-Einheiten (Einheit 5 m / s) (Einheit 6 m))))
                ;; Dies kann später gemacht werden
                (defun testfun5c ()
                  (Pluseinheiten mit Einheiten (Einheit 5 m / s) (Einheit 6 km / h)))

                OK, was ist das Ziel hier?

                • Wenn die Einheiten verfügbar sind, sollten sie überprüft werden. In der ersten Version gleich zu sein, in der ausgefalleneren Version um kompatibel zu sein. So oder so wollen wir Fehler abfangen.
                  • Wir wollen nicht eine ganze Nacht damit verbringen, dies umzusetzen.
                  • Die Prüfung sollte zur Kompilierzeit erfolgen. Der kompilierte Code sollte nichts außer einer kompilierten Nummer in Planck-Einheiten enthalten.
                  • (defmacro plus-with-units (val1 val2)
                      (lass (erste Einheit)
                        (dolist (sache (liste val1 val2))
                          (wann (listp Ding)
                            (wenn (nicht erste Einheit)
                                (setf firstunit (drittes))
                                (es sei denn (gleich erste Einheit (dritte Sache))
                                  ;; eine klare Fehlermeldung drucken. Nicht etwas Leute
                                  ;; Sie müssen in eine Webseite kopieren, um sie in Menschen zu übersetzen
                                  (Fehler "Inkompatible Einheiten: ~ a ~ a ~%")
                                         erste Einheit (dritte Sache)))))))
                      ;; verzögern Sie die Auswertung
                      `(+, val1, val2))
                    ;; arbeitet:
                    (defun testfun5a ()
                      (Pluseinheiten mit Einheiten (Einheit 5 m / s) (Einheit 6 m / s)))
                    ;; Error:
                    (defun testfun5b ()
                      (Plus-mit-Einheiten (Einheit 5 m / s) (Einheit 6 m))))

                    Der Fehler, der für den zweiten Test zur Kompilierzeit angezeigt wird, lautet:

                    crachem.lisp: 209: 3:
                      Error:
                        während der Makroerweiterung von (PLUS-MIT-EINHEITEN (EINHEIT 5 M / S) (EINHEIT 6 M)). Benutzen
                        * BREAK-ON-SIGNALS * zum Abfangen.
                                                                                                                                                                                                                                      
                         Inkompatible Einheiten: M / S M
                    Kompilierung fehlgeschlagen

                    Gut. Sieht nützlich aus.

                    Wir haben also die vollständige Sprache zur Verfügung, zur Kompilierzeit, mit printf / format und allem. Und wir können verwenden, um nützliche Fehlermeldungen beim Kompilieren zu geben. Genau wie in echtem Code. Es ist wirklich schlimm, wenn Ihre Sprache Sie zwingt, zur Kompilierzeit eine andere, verkrüppelte Sprache zu verwenden.

                    Ich möchte diesen Beitrag hier abbauen. Wie Sie sehen, ist das Makro (plus-with-units ...) nicht komplex, es müsste zumindest geprüft werden, ob das erste Listenelement tatsächlich "unit" ist. Nicht wirklich. Es sollte mit dem Makro (Einheit…) integriert werden.

                    Um dies weiter zu tun, ändern Sie das Makro (Einheit ...), damit der Programmierer damit mehr über den Makroaufruf erfahren kann. Im Moment erhalten Sie nur die umgewandelte Nummer aus dem Anruf (Einheit…). Der Anruf (Einheit…) weiß, welche Einheit verwendet wurde, Sie können ihn jedoch nicht auffordern, sie Ihnen zu geben. Während wir dabei sind, könnte uns das Makro (Einheit…) auch sagen, um welche Art von Einheit es sich handelt (Geschwindigkeit, Gewicht usw.).

                    Wir verwenden hierfür mehrere Renditen. Eine Funktion in Common Lisp kann mehr als einen Wert zurückgeben. Wenn Sie sie nicht absichtlich erfassen, werden alle außer dem ersten ignoriert. Wir möchten auch den Großteil dieses Makros in eine Funktion konvertieren, da dies einfacher zu debuggen ist. Habe ich schon erwähnt, dass Sie Funktionen definieren und zur Kompilierzeit verwenden können, aus Makros?

                    Beispielimplementierung:

                    ;; Dieser Einheitsmesser gibt drei Werte zurück:
                    ;; - der umgerechnete Wert
                    ;; - die Einheit
                    ;; - Was ist das für eine Einheit?
                    (defun unit2-helper (Werteinheit)
                      (lassen Sie *
                             (neuer Wert
                              (Falleinheit
                                (m / s (eingestellte Geschwindigkeit) (/ Wert 2.99792458e + 8))
                                (Js (setf whatkind 'Energiezeit)
                                    (/ Wert 1,054571800e-34))
                                (m (Länge der Länge) (/ Wert 1.616229e-35))
                                (s (setf whatkind 'Zeit) (/ Wert 5.39116e-44))
                                (kg (gewählte Masse) (/ Wert 2,176470e-8))
                                (: none (setf whatkind 'none) Wert)
                                (MeV / c ^ 2 (gesetzte Masse)
                                         (* Wert (Einheit 1.78266191e-30 kg)))
                                (t (Fehler "unbekannte Einheit ~ a" Einheit)))))
                        (Werte newvalue unit whatkind)))
                    ;; Dies ist das dumme Frontend, das Sie vom regulären Code aus aufrufen
                    (defmacro unit2 (werteinheit & rest mehr)
                      (deklarieren (mehr ignorieren))
                      (unit2-helper value unit))

                    Das grundlegende Makro (unit2…) verhält sich wie zuvor (unit…). Das ist es, was Sie beim Schreiben von normalem Code verwenden. Aber das neue Spielzeug gibt uns die Möglichkeit, mehr über die eingehenden (unit2…) -Anrufe zu erfahren. Wir können das nutzen, um die Dinge enorm zu verbessern.

                    (defmacro plus-with-units2 (val1 val2)
                      (lassen Sie (erste Art)
                        (dolist (sache (liste val1 val2))
                          (when (und (listp thing) (gleich (erstes) 'UNIT2))
                            (multiple-value-bind (newvalue unit whatkind)
                                (unit2-helper (zweitens) (drittes))
                              (Druck waskind)
                              (wenn (nicht auf den ersten Blick)
                                  (setf firstkind whatkind)
                                  (es sei denn (gleich Firstkind Whatkind)
                                    ;; eine klare Fehlermeldung drucken. Nicht etwas Leute
                                    ;; Sie müssen in eine Webseite kopieren, um sie in Menschen zu übersetzen
                                    (Fehler "Inkompatible Einheiten: ~ a ~ a ~%")
                                           firstkind whatkind)))))))
                      ;; verzögern Sie die Auswertung bis zu einem späteren Zeitpunkt
                      `(+, val1, val2))
                    ;; Das funktioniert jetzt, der Code erkennt, dass kg und MeV / c ^ 2
                    ;; sind beide Einheiten der gleichen Art - Masse
                    (defun testfun6 ()
                      (Plus-mit-Einheiten2 (Einheit2 5 kg) (Einheit2 6 MeV / c ^ 2)))

                    Stellen Sie sicher, dass alles zur Kompilierzeit geschieht:

                    Ja Meister? CL-USER> (zerlegen 'testfun6)
                    ; Demontage für TESTFUN6
                    ; Größe: 13 Bytes. Herkunft: # x52E3B9B6
                    ; B6: 488B15B3FFFFFF MOV RDX, [RIP-77]; Einstiegspunkt für das Parsen ohne Argumente
                                                              ; 2.297298e8
                    ; BD: 488BE5 MOV RSP, RBP
                    ; C0: F8-CLC
                    ; C1: 5D-POP-RBP
                    ; C2: C3 RET
                    NULL
                    Ja Meister? CL-USER>

                    Gut aussehen.

                    Weitere Arbeit:

                    Wie machen Sie das mit Werten, die über Dateien eingehen? Ganz einfach wickeln Sie Makros um die zeilenweisen Dateileser, die angeben, in welchem ​​Feld sich welche Einheit befindet. Die Einheit wird dann beim Öffnen der Datei einmal geprüft. Da sie jedoch weiß, dass der über die Zeilen durchlaufende Codeblock keine Einheiten ändert, muss die Überprüfung nicht erneut durchgeführt werden. Das Makro fügt die Konvertierungsmathematik in den Code des durch Zeilen iterierenden Codekörpers ein. So kannst du so etwas tun:

                    (mit -Einheitsdatei-Feld ("foo.txt" ((Masse: Spalte 1: Einheit kg)))
                      (+ * bla * Masse)); Variable Masse wird in Planck-Einheit umgerechnet
                    ;; Wenn Sie bereit sind, durch den Code zu gehen:
                    (mit -Einheitsdatei-Feld ("foo.txt" ((Masse: Spalte 1: Einheit kg)))
                      (+ * bla * (* (Einheit 8 kg) Masse)))
                    ;; das würde einen Fehler werfen, wenn Masse keine Masseneinheit wäre

                    Dieser Evaluator kann Algebra für den Text der Einheiten ausführen, so dass Sie sie automatisch konvertieren können, nachdem Sie nur die meisten grundlegenden Einheiten definiert haben. Ich möchte jedoch, dass dies ein generischerer Algebra-Konverter ist. So möchte ich zum Beispiel mathematische Funktionen nicht durch mehrere Variablen auflösen und diese Funktionen dann in den Quellcode eingeben. Ich möchte die Funktion einmal eingeben und dann einem Makro mitteilen, dass es einige Funktionen generiert, die durch verschiedene Variablen aufgelöst werden. Mein Scheimpflug-Code für die Shift-Tilt-Fotografie schreibt nicht selbst, sollte aber in Lisp.