Metasprachen lassen sich anhand von drei Merkmalen klassifizieren. Das erste Merkmal beschreibt den Bearbeitungszeitpunkt des Objektprogramms. Je nachdem, ob das Objektprogamm also zur Laufzeit oder zur Compilezeit generiert wird, unterscheidet man zwischen dynamischen und statischen Metasprachen.6 Als Beispiel für eine dynamische Metasprachen kann die Programmiersprache Java unter Verwendung der Reflection Api gesehen werden. Im Gegenteil dazu ist Template Haskell rein Compilezeit-orientiert - also statisch.
Ein zweites Klassifizierungsmerkmal beschreibt die Heterogenität von Objekt- und Metasprache. Heterogene Metasprachen sind solche, welche nicht die gleiche Sprache verwenden wie das Objektprogramm.7 Als Beispiel dafür kann die bereits weiter oben erwähnte Programmiersprache PHP zur Erzeugung von Java Script Code gesehen werden. Template Haskell ist dagegen eine homogene Metasprachen, da sie Haskell verwendet um Haskell zu erzeugen.
Das dritte und letzte Merkmal zur Klassifizierung von Metasprachen ist die Stufigkeit. Mehrstufige Metasprachen erzeugen Objektprogramme, die ihrerseits wiederum in einer Metasprache verfasst sind.8 Template Haskell ist eine einstufige Metasprache, da aus einem Template Haskell Metaprogramm ausführbarer Haskell Code erzeugt wird.
Unter einem Scope kann ein in sich abgeschlossener Bereich oder Kontext betrachtet werden, innerhalb dessen bestimmte Bindings gelten. Ein Binding ist die Zuweisung eines Ausdrucks (oder allgemein: Objekt) an einen Namen. Scoping Regeln beschreiben, wie und von wo aus im Programm die Bindings eines Scopes zugreifbar sind. Prinzipiell lässt sich zwischen dem statischen (lexikalischen) Scoping und dem dynamischen Scoping unterscheiden, auf deren Unterschiede im Folgenden eingegangen wird.9
Statisches Scoping
Bei Sprachen, die statisches Scoping verwenden, werden die Bindings zwischen Namen und Objekten zur Compile-Zeit bestimmt. Der Kontrollfluss des Programms zur Ausführungszeit wird somit nicht berücksichtigt. Das "aktuell" gültige Binding für einen Namen findet sich in jener Matching-Deklaration, welche den aktuellen "Punkt" im Programm am nächsten umschließt, man könnte auch sagen: der innerste Block bildet den Scope. Haskell verwendet statisches Scoping, wie in dem folgenden Beispiel ersichtlich wird10:
x = 0
bar = x
foo = let x = 1 in bar -- x wird lokal definiert
Die Redefinition von x in der Funktion foo wird vom let-Block am nächsten umschlossen und gilt daher lokal innerhalb diesem. In foo wird der Ausdruck bar ausgewertet, der ein Top-Level-Binding aufweist, welchem wiederum das Top-Level-Binding von x zugewiesen ist. Da die Top-Level-Definition von x in einem äußeren Scope ist, wird diese nicht vom let-Block beeinflusst. Ein Aufruf der Funktion foo liefert somit das Ergebnis: 0.
Dynamisches Scoping
Die Vorgehensweise beim dynamischen Scoping lässt sich anschaulich mithilfe eines Stacks beschreiben. Jeder Name hat einen Stack von Bindings, wobei stets das oberste Element eines solchen Stacks das aktuelle Binding für den zugehörigen Namen darstellt. Beim Finden eines neuen Bindings, wird dieses auf den Stack gelegt. Beim Verlassen eines Kontrollflusses (bspw. einer Funktion) wird das oberste Element vom Stack entfernt. Bezogen auf das obige Beispiel, führt dies dazu, dass der Binding-Stack für x beim Betreten der Funktion foo zunächst nur die 0 als oberstes Element enthält. Die Definition von x in foo legt die 1 auf den Stack. Der Aufruf von bar in foo liefert dann für x das nun oberste Element 1, da der Kontrollfluss von foo noch nicht beendet ist. 11
let x = \x -> x+1 in x
let x_77 = \x_78 -> x_78 + 1 in x_77
(C) Philipp Pribbernow