Im vorigen Kapitel haben wir uns schon mit einigen Stilfragen
befasst, z.B. mit dem offensichtlich stark verbreiteten Drang,
alles Mögliche mit
(setq) irgendwelchen Variablen zuzuweisen,
die keiner braucht und die auch nie wieder abgefragt werden.
Und wer schön aufgepasst hat, wird auch nie wieder ganz
verschiedene Aufgabenstellungen in unschuldigen Funktionen
vermischen... Versprochen?
In diesem Kapitel soll es um Stilblüten gehen - Codeschnipsel,
die absolut nichts bringen, aber vielleicht dem Prozessor das
Leben ein wenig schwerer machen. Wahrscheinlich erschweren sie
auch das Lesen eines Programms! Solche Stilblüten entstehen
fast immer aus mangelndem Wissen um Einzelheiten. Manchmal ist
auch Unsicherheit im Spiel - man vertraut einfach bestimmten
Mechanismen nicht.
Viele solcher Stilblüten entstehen, weil Unklarheiten darüber
bestehen, was
nil und
T sind. Werfen wir doch mal
einen Blick darauf:
T ist NICHT das Gegenteil von
nil,
das ist zunächst einmal die wesentliche Aussage! Was wir wissen
müssen: AutoLisp kennt keine sog. Booleschen Variablen, also
solche Variablen, die per Deklaration nur die beiden Zustände
'wahr' oder 'falsch' annehmen können.
Die Regel ist einfach: Alles, was zu
nil evaluiert, ist
falsch oder unwahr. Natürlich hat das wenig damit zu tun, was
im realen Leben der Fall ist - aber dazu noch einmal etwas weiter
unten. Eines ist auf jeden Fall falsch bzw. unwahr: der Umkehrschluss,
dass alles, was wahr ist, auch
T ist (wobei
T für das
englische Wort
true steht).
Die Gegenregel ist genauso einfach: Alles, was nicht zu
nil
evaluiert, ist wahr! Noch einmal: Es geht hier nicht um eine exakte
Abbildung der Realität - es geht nur um eine Vereinbarung! Mit
'wahr' und 'falsch' beschreiben wir einfach zwei Zustände, damit
wir sie voneinander unterscheiden können - sonst nichts.
In der englischen Sprache liegen die Dinge einfach:
nil, was
übrigens die Abkürzung für 'not-in-list' oder auch 'nothing-in-list'
ist, kann man mit 'nothing' umschreiben. Andere Programmiersprachen
kennen 'nothing' durchaus als Schlüsselwort für leere Listen,
nicht-initialisierte Variablen und dergleichen. Das Gegenteil ist
'anything' - genau das wird vom AutoLisp-Interpreter als 'wahr'
eingestuft. Auf deutsch: 'Etwas', 'irgendwas' ... Mir persönlich
gefällt da das 'Ichts' am besten - ok, ok, nie gehört. Scheint ein
etwas untergegangenes Wort zu sein (s. dazu de.rec.sprache.deutsch
am 21.6.2002). Trotzdem bleibe ich dabei:
nil ist das Nichts,
alles andere ist das Ichts.
Und was hat nun
T damit zu tun?
T ist ein Symbol,
nichts weiter. Es gibt aber eine Art Vereinbarung, dass Funktionen das
Symbol
T zurückgeben, wenn inhaltlich ein
true gemeint
ist. Was würde sich ändern, wenn alle Funktionen, die bisher im Wahr-Fall
T zurückgeben, ab morgen
X oder
SCHOENES-WETTER-HEUTE
zurückgäben? Nichts! Entweder gibt eine Funktion
nil zurück,
dann wissen wir, dass es der Falsch-/Unwahr-/Nichts-Fall war,
oder sie gibt etwas anderes zurück - dann war es der Wahr-/Richtig/Ichts-Fall.
Aber was die Funktion im Einzelnen zurückgibt, spielt keinerlei Rolle.
Und damit kommen wir zur AutoLisp-Stilblüte schlechthin:
(if (/= irgendeine-liste nil)
(dann-mach-weiter ...)
(dann-lass-stecken ...)
)
Wer die Anfangskapitel dieses Tutorials aufmerksam gelesen hat, kennt
die Regeln: Die Argumente werden von links nach rechts verarbeitet, also
ist die Klammer mit der Funktion
(/= ...) zuerst dran.
irgendeine-liste evaluiert dann zu
nil, wenn die Liste
leer ist. Was testen wir dann? Es wird getestet, ob
nil ungleich
nil ist. Das kann auf jden Fall nur 'wahr' ergeben.
Wesentlich ist hier das Wissen, dass
irgendeine-liste sowieso
zu 'wahr' evaluiert, wenn sie nicht leer ist. Also warum nicht gleich
so:
(if irgendeine-liste
(dann-mach-weiter ...)
(dann-lass-stecken ...)
)
Mehr ist nicht nötig - alles andere bremst nur und macht den Code
unleserlich! Eine ähnliche Unsauberkeit ist die Rückgabe eines
expliziten
nil, wenn sowieso schon
nil zurückgegeben
wird. Sehen wir uns ein Beispiel an:
(defun teste-was(daten / ergebnis)
(setq ergebnis(machwas-mit daten))
(if(/= ergebnis nil)
ergebnis
nil
)
)
Auch hier werden unsinnige Vergleiche durchgeführt. Entweder
enthält
ergebnis etwas ('wahr') oder nicht (nil). Statt
der ganzen
(if ...)-Konstruktion können wir also einfach
ergebnis hinschreiben, ohne dass sich etwas ändert:
(defun teste-was(daten / ergebnis)
(setq ergebnis(machwas-mit daten))
ergebnis
)
Aber auch das ist noch zu viel! Die Rückgabe von
(setq)
ist doch identisch mit der Rückgabe von
ergebnis. Es kann
also weiter gekürzt werden:
(defun teste-was(daten / ergebnis)
(setq ergebnis(machwas-mit daten))
)
Die Funktion wird unbeeindruckt weiterlaufen. Und nun natürlich
die letzte Frage: Wozu eigentlich
(setq)? Weg damit:
(defun teste-was(daten / )
(machwas-mit daten)
)
Auch diese vierte Version der Funktion verhält sich noch immer
identisch zur ersten!
Nun noch einmal zum Symbol
T und dem oft hilflos wirkenden
Umgang damit. Wie bereits gesagt, ist
T nicht das Gegenteil
von
nil. Es ist nur eine Konvention, dass Testfunktionen
meistens das Symbol
T zurückgeben, wenn 'wahr' angezeigt
werden sollte. Wie immer, wenn wir mit Symbolen selbst arbeiten,
aber nicht an evtl. daran gebundenen Werten interessiert sind,
müssen wir
T also mit einem Quote-Zeichen versehen, wenn
wir es selbst verwenden. Als Beispiel eine kleine Funktion, in der
mit Symbolen ohne Wertbindung gearbeitet wird:
(defun kategorien(zahl / )
(cond
( (> zahl 10000)'riesengross)
( (> zahl 1000)'gross)
( (> zahl 100)'mittel)
('T 'klein) ; so ist's korrekt!
)
)
Hier wird das Symbol
T korrekt verwendet - ohne das
Quote-Hochkomma würde das Symbol nämlich evaluiert, was aber
überhaupt nicht nötig oder erwünscht ist! Die neueren
AutoCAD-Versionen leisten dieser Stilblüte nun auch noch
Vorschub: das Symbol
T ist nun geschützt, kann nicht
mehr verändert werden und evaluiert zu sich selbst. Damit hat
aber dieses Symbol nun einen Sonderstatus, der der Sache gar
nicht entspricht: irgendeine Funktion hat
T nach wie
vor nicht, da die Booleschen Variablen ja keinen Datentyp
darstellen.
Die einen werden sagen: Ist doch praktisch, jetzt kann da
wenigstens nichts mehr passieren! Es ist aber all die Jahre
nichts passiert, wenn man ordentlich gearbeitet hat - es war
möglich, eine Variable trotzdem
T zu nennen usw.
Passiert ist nur eines: Wer exzessiv mit Symbolen arbeitet (z.B.
als Assoziationsschlüssel für Listen), kann sich nicht mehr
darauf verlassen, dass das problemlos läuft - irgendwo
könnte ja ein Symbol entstehen, das zufällig
T heisst,
und dann bleibt das Programm eventuell mit einer Fehlermeldung
stehen.
Eine weitere stark verbreitete Stilblüte ist das zuweisen
von
nil an Variablen, die sowieso nichts enthalten:
(defun eine-funktion(arg / var1 var2)
(setq var1 '())
(setq var2 nil)
...
...
)
Die beiden Zeilen im Funktionsrumpf haben absolut keine Wirkung,
sie vebrauchen nur einfach Rechenzeit. Und warum werden
zwei verschiedene Erscheinungsformen von
nil verwendet?
Offensichtlich glaubte der Autor dieser Zeilen, dass die
erste
(setq)-Anweisung damit aus
var1 eine leere
Liste gemacht wird, aus
var2 hingegen eine andere
Variable. Das ist aber schlicht und einfach Unsinn! Jeder
Datentyp in AutoLisp kann an jedes Symbol gebunden werden
(mit Ausnahme von
T neuerdings).
Machen wir die Dinge noch einmal klar: Jedes verwendete Symbol,
dem bisher kein Wert zugewiesen wurde, ist an
nil gebunden.
Da aber eine leere Liste identisch mit
nil ist, sind
ungebundene Symbole de facto immer leere Listen! Technisch
gesehen ist es aber so, dass es in AutoLisp überhaupt keine
leeren Listen gibt - leere Listen sind nichts weiter als ein
gedankliches Konzept, aber kein technisches!
Und nun zur letzten Stilblüte, die ich hier anführen möchte:
Wenn es um die Rückgabe einer Funktion geht, sind manchmal
akrobatische Code-Verrenkungen zu beobachten:
(defun noch-eine-funktion(daten / element ergebnis)
(foreach element daten
(setq ergebnis ....)
)
(reverse(reverse ergebnis))
)
; Die letzte Zeile könnte auch
; so lauten:
(car(list ergebnis))
; oder so (habe ich auch
; schon gesehen):
(append ergebnis)
Darüber, dass hier
element zusätzlich als lokale
Variable deklariert wurde, obwohl die andere Variable
element bereits zu
foreach lokal ist, sehen
wir einmal hinweg. Wesentlicher ist die Frage, warum
ergebnis am Ende der Funktion zweimal umgedreht
wird: Scheinbar wusste der Autor einfach nicht, wie man
eine einfache Rückgabe aus einer Variablen erzeugt: Man
lässt einfach den Variablennamen als Ausdruck evaluieren!
(defun noch-eine-funktion(daten / ergebnis)
(foreach element daten
(setq ergebnis ....)
)
ergebnis
)
So sieht die Funktion also 'komplett bereinigt' aus.
Natürlich gibt es noch eine ganze Menge mehr solcher
unsinniger Konstruktionen - ich habe mich nur bemüht,
ein paar besonders repräsentative Exemplare herauszugreifen.
Übungsaufgaben
Schauen Sie sich diese Funktion an. Finden Sie heraus, was
Sie tut, kritisieren Sie sie, und schreiben Sie dann eine
neue Version, die den gleichen Zweck erfüllt (lassen Sie
aber das weg, was nicht hineingehört).
(defun abcde(zzz / liste elem pkt1 pkt2 pliste)
(setq pliste nil)
(setq elem zzz)
(command "_UCS" "_W")
(setq pkt1 (cdr (assoc 10 elem)))
(setq pkt2 (cdr (assoc 11 elem)))
(setq pliste (append pliste (list pkt1)))
(setq pliste (append pliste (list pkt2)))
(setq ppliste (append ppliste (list pliste)))
)
Lösungen