Bohnenmaschinen-Retrospektive, Teil 6
Frohes neues Jahr euch allen!
Das letzte Mal habe ich kurz die Grundstrategie des Beanstalk-Compilers beschrieben: Transformieren Sie den Quellcodejeder abgefragten oder beobachteten Funktion (und transitiv ihrer aufgerufenen Funktionen) in ein äquivalentes Programm, dasteilweise wertet ausdas Modell,Akkumulieren eines Diagrammswie es geht. In diesem Beitrag werde ich den ersten Schritt der Transformation durchgehen.
Die Idee der ersten Quelle-zu-Quelle-Transformation besteht darin, eine relativ komplizierte Sprache – Python – durch das zu ersetzen, was ich als „vereinfachtes Python“ bezeichne. Im Zen von Python heißt es bekanntlich: „Es sollte einen – und vorzugsweise nur einen – offensichtlichen Weg geben, dies zu tun” aber das ist nicht das Ziel dieses Vereinfachungsschrittes. Das Zen von Python betont Offensichtlichkeit. Es geht darum, den Code klar und die Sprache erkennbar zu machen. Mein Ziel mit vereinfachtem Python ist „Es sollte nur einen Weg geben, dies zu tun” – weil es einfacher ist, ein Programm so zu instrumentieren, dass es seine Bedeutung während der Ausführung erfasst, wenn es nur eine Möglichkeit gibt, alles zu erledigen.
Überlegen Sie beispielsweise, wie Sie eine Funktion foo mit drei Argumenten aufrufen:
foo(1, 2, 3)
x = [1, 2, 3)
foo(*x)
foo(1, 2, bar=3)
x = {"bar":3}
foo(1, 2, **x)
… und so weiter – es gibt viele offensichtliche Möglichkeiten, eine Funktion in Python aufzurufen, aber ich möchte, dass es genau eine in vereinfachtem Python gibt.
Das erste, was wir bei der Analyse einer abgefragten oder beobachteten Funktion tun, besteht darin, ihren Quellcode abzurufen, diesen in einen abstrakten Syntaxbaum (AST) zu analysieren und dann eine Reihe von AST-Transformationen durchzuführen, die die Funktion in ein äquivalentes, aber einfacheres Python-Programm reduzieren. Dies ist der traditionellste Compiler-Schritt in der Pipeline.
(Wie erhalten wir den Quellcode? Denken Sie daran, dass wir für jede Beobachtung und Abfrage eine RVID haben; die RVID enthält eine Referenz auf das ursprüngliche Funktionsobjekt. Pythons Laufzeit kann den Quellcode für eine bestimmte Funktionsreferenz bereitstellen.)
Schauen wir uns eine nur etwas kompliziertere Version unseres „Hallo Welt“-Modells an:
@random_variable
def fairness():
return Beta(2,2)
@random_variable
def flip(n):
return Bernoulli(0.5 * fairness())
In unserem vereinfachten Python:
- Jeder Wert bekommt sein eigenes Variable
- Jeder Berechnung bekommt sein eigenes Stellungnahme
- Jeder Anruf hat das gleiche Syntax:
a = b(*c, **d)
Unsere beiden Methoden in vereinfachter Form wären:
def fairness():
_t1 = 2
_t2 = 2
_t3 = [_t1, _t2)
_t4 = {}
_t5 = Beta(*_t3, **_t4)
return _t5
def flip(n):
_t1 = 0.5
_t2 = [)
_t3 = {}
_t4 = fairness(*_t2, **_t3)
_t5 = _t1 * t4
_t6 = [_t5)
_t7 = {}
_t8 = Bernoulli(*_t6, **_t7)
return _t8
NEBENBEI: Leser mit etwas Erfahrung im Schreiben von Compilern könnten sich fragen: „Ist das eine statische Einzelzuweisungsform?“ Das SSA-Formular ist eine Transformation, bei der jede Variable genau einmal zugewiesen wird; Es wird für die Kontrollflussanalyse und andere Programmanalysen verwendet. Ich habe keine vollständige SSA-Transformation geschrieben, aber als ich das erste Prototyping durchführte, wusste ich, dass ich in Zukunft möglicherweise ein SSA-Formular benötigen würde. Betrachten Sie es als SSA-light, wenn das Sinn macht.
Wir können noch weiter gehen; in unserem vereinfachten Python:
- logische Operatoren
andUndorwerden beseitigt
x = y and z
kann vereinfacht werden zu
x = y
if x:
x = z
und jetzt muss sich der Graphenakkumulator keine Sorgen mehr machen überhaupt über logische Operatoren, da diese im vereinfachten Programm nicht vorhanden sind.
- Jeder
whileSchleife istwhile True.
- Keine Schleife hat eine
elseKlausel. (Wussten Sie, dass Python-Schleifen…elseKlauseln? Es ist wahr!)
- Es gibt keine zusammengesetzten Vergleiche:
x = a < b < c
kann vereinfacht werden zu
x = a < b
if x:
x = b < c
und jetzt muss sich der Graphenakkumulator nicht mehr um zusammengesetzte Vergleiche kümmern, da es keine gibt.
- Ebenso gibt es keine Lambdas, keine Funktionsanmerkungen und so weiter. Alle praktischen syntaktischen Zucker werden in ihre grundlegendere Form entzuckert.
Die vereinfachte Sprache ist definitiv keine Sprache, in der ich gerne ein langes Programm schreiben würde, aber Es ist eine Sprache, die sich leicht programmgesteuert analysieren lässt. Bisher machen die Transformationen das Programm länger und einfacher, ändern aber nichts an seiner Bedeutung; Wenn wir die vereinfachte Version des Programms kompiliert und ausgeführt haben, sollte es dasselbe tun wie die normale Version.
Das nächste Mal auf FAIC: Lassen Sie uns näher auf die Funktionsweise des AST-zu-AST-Transformationscodes eingehen. Es hat mir viel Spaß gemacht, es zu schreiben.
