Was ist Code, genauer: Quellcode? Wie üblich, läßt die Wikipedia keinen Zweifel zu:
In computer science, source code is text written in a computer programming language.
Interessanterweise ist die Common Lisp HyperSpec anderer Meinung:
source code n. code representing objects suitable for evaluation (e.g., objects created by read, by macro expansion, or by compiler macro expansion).
wobei code wie folgt definiert wird:
code n. 1. Trad. any representation of actions to be performed, whether conceptual or as an actual object, such as forms, lambda expressions, objects of type function, text in a source file, or instruction sequences in a compiled file. This is a generic term; the specific nature of the representation depends on its context. 2. (of a character) a character code.
In der Tat ist Common Lisp vielleicht die einzige halbwegs verbreitete Sprache, die Code nicht als eine Form von Text definiert. Was also ist Quellcode im Sinne der HyperSpec, und inwiefern ist das Konzept nützlich?
Code is Data
Nach der HyperSpec-Definition besteht Code aus auswertbaren Objekten. Das Wörtchen „Objekte“ impliziert offenbar, daß es sich um Daten handelt; aber was für Arten von Objekten sind auswertbar?
Die Antwort ist möglicherweise überraschend: alle! Jedes Lisp-Objekt ist auswertbar, auch wenn die Auswertung in der Praxis mal nützlicher und mal weniger nützlich ist. Im Kontext der Auswertung heißen Objekte auch Formen. Die HyperSpec unterscheidet davon drei Arten: Symbole, (nichtleere) Listen und selbstauswertende Objekte. Jedes Objekt, das weder ein Symbol noch eine Liste ist, ist selbstauswertend, das heißt: Das Ergebnis der Auswertung eines solchen Objekts ist das Objekt selbst.
Man kann sich das anhand der Funktion eval
leicht veranschaulichen:
(eval (list 'print "Hello world!")) ;gibt „Hello, world!“ aus (eval 100) ;=> 100 (eval (make-hash-table)) ;=> #<a hash table>
Es mag etwas lächerlich anmuten, Verbünde, Hashtables und Instanzen von Klassen als Code anzusehen, aber hin und wieder kommt man in eine Situation, in der das Einbetten von Datenobjekten nützlich ist. Betrachten wir zum Beispiel folgenden Code:
(defun find-mulkey-word (string) (scan "([Mm]ulk|[Kk]lum|Munoca)" string))
Nur ein normaler Funktionsaufruf, nicht wahr? Könnte man meinen. In Wirklichkeit liegt über der Funktion scan
ein sogenanntes Compilermakro. Compilermakros sind optionale Optimierungen, die der Compiler aufrufen kann. In diesem Fall ist das Resultat, daß der obige Code zum folgenden makroexpandiert:
(defun find-mulkey-word (string) (scan (load-time-value (create-scanner "([Mm]ulk|[Kk]lum|Munoca)")) string))
Was ist nur das jetzt wieder Seltsames? load-time-value
sorgt dafür, daß die create-scanner
-Form zur Ladezeit ausgeführt wird, also dann, wenn der Code in das Lispprogramm geladen wird. Das Ergebnis (in diesem Fall ein kompiliertes Scanner-Objekt) wird dann anstelle der load-time-value
-Form direkt in den Code eingebettet. Das Resultat ist, daß der reguläre Ausdruck nur einmal kompiliert werden muß statt jedesmal, wenn die Funktion aufgerufen wird. Bei einer dermaßen nützlichen Funktion, die wahrscheinlich tausende Male in der Minute aufgerufen wird, bringt das schon einen wesentlichen Performancegewinn.
Natürlich — ähnliches geht in C mit einer static
-Variablen oder in Java mit einem Singleton. Aber mal im Ernst: Vergleichbar elegant ist das nicht. Und ist es nicht schön, sich nicht selbst um solcherlei Technikalien kümmern zu müssen, weil der Autor der Bibliothek ein Werkzeug in der Hand hatte, um es einem abzunehmen?
Schreiben wir den Code als der einfache Funktionsaufruf, der er ist, und lehnen uns zurück und genießen die Sonne.
Ah. So ist das Leben angenehm.
Comments
Submit a comment
Note: This website uses a JavaScript-based spam prevention system. Please enable JavaScript in your browser to post comments. Comment format is plain text. Use blank lines to separate paragraphs.