Wir sollten uns nun noch einmal den Testfunktionen zuwenden,
die uns AutoLISP zur Verfügung stellt. Wir haben bereits einige
Funktionen kennengelernt:
(listp ...) und
(atom)
wurden in den ersten Kapiteln über Listen vorgestellt, wobei wir
nie vergessen sollten, dass beide Funktionen bei der Anwendung
auf
nil ein
T zurückgeben. Die Vergleichsfunktionen
wie
(> ...),
(< ...) und
(= ...)
wurden bereits im ersten der Kapitel über Programmverzeigungen
besprochen.
Wenn bisher von Datentypen die Rede war, dann wurde dieser
Begriff verwendet, ohne genau zu klären, welche Datentypen es
in AutoLISP eigentlich gibt. Dies soll nun nachgeholt werden. Bereits
bekannt sind Integer- und Realzahlen sowie Zeichenketten, wenn auch
mit Zeichenketten bisher noch nicht viel gemacht wurde. Diese
Datentypen haben die Eigenschaft, dass sie zu sich selbst evaluieren,
d.h. mit anderen Worten, wir können sie sowohl quoten als auch
evaluieren, ohne dass sich irgend etwas ändert. Ferner kennen wir
den Datentyp Liste, wobei es keinen Unterschied macht, ob es sich
um Datenlisten oder Programmcode handelt.
Die eingebauten Funktionen von AutoLISP stellen einen weiteren
Datentyp dar, und ab AutoCAD 2000 werden auch mit
(defun ...)
erzeugte Funktionen diesem Datentyp zugeschlagen. In beiden Fällen
sind es keine Listen, und wir haben auch keinen Zugriff auf ihren
Programmcode. Ein weiterer uns bekannter Datentyp sind die Symbole.
Die Funktion
(type ...) ermittelt den Datentyp eines Ausdrucks.
(type) führt keine automatische Quotierung des Ausdrucks
durch, da sonst die Antwort verfälscht würde.
(type ...) gibt
als Antwort eines der folgenden Symbole zurück (die Liste ist nicht
ganz vollständig):
INT - Integerzahl
REAL - Realzahl
STR - Zeichenkette
LIST - Liste
SYM - Symbol
SUBR - Eingebaute Funktion
USUBR - Benutzer-Funktion (seit AutoCAD 2000)
FILE - Datei-Handle
EXSUBR - Über die ADS def. Funktion
EXRXSUBR - Über RX def. Funktion
PICKSET - Auswahlsatz
ENAME - Entity-Name
nil - Leere Liste oder nil-Symbol
Wir wollen uns hier zunächst auf die wichtigsten Möglichkeiten
beschränken und sie anhand einiger Beispiele erläutern (Ihre
selbstdefinierte Funktion
(sqr) ist noch geladen, wenn
nicht, bitte noch einmal schnell
(defun sqr(zahl)(* zahl zahl))
eingeben:
(type 0) => INT
(type 0.0) => INT
(type 1.93) => REAL
(type pi) => REAL
(type 'pi) => SYM
(type type) => SUBR
(type 'type) => SYM
(type "Zeichenkette") => STR
(type (* 3 4.5)) => REAL
(type '(* 3 4.5)) => LIST
(type unbekanntes-symbol) => nil
(type 'unbekanntes-symbol) => SYM
;auch ein Symbol ohne Bindung ist ein Symbol
(type 'a) => SYM
(type ''a) => LIST
;= (QUOTE(QUOTE A)), nur 1 Stufe wird evaluiert
(type sqr) => LIST
; Bis AutoCAD 14
(type sqr) => USUBR
; Ab AutoCAD 2000. Wenn die Funktion in der
; Kommandozeile eingegeben wurde, kann statt
; dessen auch SUBR erscheinen!
(type nil) => nil
(type '()) => nil
Die Bedeutung der anderen Rückgabesymbole wird noch in
den nächsten Kapiteln behandelt. Sie kann aber auch in der
AutoCAD-Hilfe nachgeschlagen werden.
Mit der
(type)-Funktion können wir viele Auswahl- oder
Entscheidungsprozesse steuern. Zwei der wesentlichen Aufgaben
von
(type) sind, sich Informationen über den Inhalt von
Listen oder den Typ von übergebenen Argumenten zu beschaffen.
Wenn Sie eine eingebaute Lisp-Funktion mit falschem Argument
aufrufen, erhalten Sie sofort eine entsprechende Fehlermeldung.
Bei Ihren selbstgeschriebenen Funktionen findet eine solche
Prüfung nicht statt, denn Sie legen in der formalen Argumenteliste
ja nur Anzahl und funktionsinterne Namen der Argumente fest, nicht
aber deren Datentyp.
Lisp hindert Sie also nicht daran, z.B. (sqr "Käsebrot")
einzugeben. Die Funktion
(sqr) hat dummerweise keinerlei
Ahnung, was sie da an
(* ...) weiterleitet. Erst der
Multiplikation wird das Käsebrot im Halse steckenbleiben.
Normalerweise können Sie auf eine selbstgestrickte Typprüfung
verzichten. Wenn Ihre Programme halbwegs durchdacht sind, dann können
Sie darauf vertrauen, dass
(sqr) immer nur mit Zahlen
aufgerufen wird. Sie wissen ja, welche Variablen Zahlen enthalten
und werden
(sqr) nur auf diese Variablen anwenden. AutoLISP
bietet auch eine ganze Reihe von Benutzereingabe-Funktionen, die
schon im Vorfeld verhindern, dass der Benutzer statt der erwarteten
Zahl "Käsebrot" eingibt.
Es treten aber auch immer wieder Situationen auf, in denen Sie
nicht mehr genau wissen, was eine Funktion oder ein Ausdruck da
zur Verarbeitung erhalten wird. Der einfachste Fall ist, dass
eine Liste, die zur Verarbeitung ansteht, möglicherweise leer
sein könnte. Das liesse sich so formulieren:
(if (= (type unbekannte-liste) nil)(...))
Kürzer wäre hier allerdings die Verwendung von
(not)
bzw.
(null):
(if (not unbekannte-liste)(...))
(if (null unbekannte-liste)(...))
Die Funktionen
(not) und
(null) sind in der Wirkung
gleich, beide geben dann
T zurück, wenn die Evaluation des
Argumentes nil ergibt. Aus traditionellen Gründen wird meistens
(null) für Listen angewendet und
(not) bei anderen
Objekten und in logischen Verknüpfungen.
Beim Umgang mit den Testfunktionen müssen Sie sich mit feinen
Unterschieden befassen. Brauchen Sie einen Test, der nur prüft,
ob etwas eine Liste ist, dann verwenden Sie
(listp). Dieser
Test wird auch leere Listen, also nil, durchgehen lassen. Benötigen
Sie jedoch einen Test, dann ist die Formulierung
(if (= (type unbekannte-liste) 'list) (...))
angebracht. Sie sollten hier übrigens das Quote-Zeichen vor dem
Symbol
list beachten. Es muss sein, da Sie ja die Rückgabe
mit dem Symbol
list vergleichen wollen, nicht mit einem
darangebundenen Wert (das wäre der Funktionsinhalt der Funktion
(list)).
Hier noch einige Bespiele, wie man Verzweigungen und Schleifen in
einem Programm von Testfunktionen abhängig machen kann:
(setq zahl 100)
(while (not (zerop zahl))
...
(setq zahl(1- zahl))
)
; Diese Schleife wird 100 mal durchlaufen.
; Dabei wird die Zahl 100 mal heruntergez„hlt.
; Sie hat also nach dem Ende der Schleife
; den Wert 0!
Im nächsten Beispiel verwenden wir
(minusp). Diese Funktion
testet, ob eine Zahl negativ, d.h. kleiner als 0 ist. Ausserdem wird
hier die ebenfalls neue Funktion
(1- zahl) verwendet:
Hier geht es um das Dekrementieren einer Zahl, es wird also 1 von
zahl abgezogen.
(1- zahl) ist also im Prinzip
das Gleiche wie
(- zahl 1). Die Gegenteils-Funktion
heisst natürlich
(1+ zahl) und inkrementiert die Zahl.
Dass es diese Funktionen gibt, ist eher historisch zu sehen. In der
Zeit, als Lisp entwickelt wurde, sorgten diese beiden Funktionen
dafür, dass statt einer wirklichen Subtraktion bzw. Addition statt
dessen die Inkrement-/Dekrement-Funktion der Maschinensprache des
Rechners ausgeführt wurde, um Rechenzeit zu sparen. Heutzutage spielen
solche Überlegungen keine Rolle mehr, da aber die beiden Funktionen
in vielen Lisp-Programmen verwendet werden, möchte ich sie hier
vorstellen.
(setq laenge (1-(length zahlenliste))
(setq liste-ist-ok nil)
(while (not (minusp laenge))
(if
(or
(/= (type (nth laenge zahlenliste))'real)
(/= (type (nth laenge zahlenliste))'int)
)
(progn
(setq liste-ist-ok nil)
(setq laenge -1)
)
(progn
(setq liste-ist-ok 'T)
(setq laenge(1- laenge))
)
)
)
Dieses Beispiel testet, ob in der Zahlenliste wirklich nur
Integer- und Realzahlen vorhanden sind. Eine leere Liste soll
von diesem Test abgelehnt werden. Etwas störend ist nur die
Umständlichkeit der Prüfung. Daher eine zweite Version, die den
Code etwas strafft:
(setq laenge (1- (length zahlenliste))
(setq liste-ist-ok nil)
(while (not (minusp laenge))
(if
(not(member(type(nth laenge zahlenliste))'(INT REAL)))
(setq laenge -1 liste-ist-ok nil)
(setq laenge (1- laenge) liste-ist-ok 'T)
)
)
Wir sollten uns über diese beiden Programmfragmente noch ein
paar Gedanken machen. Warum ist es hier nötig, die Ergebnisvariable
liste-ist-ok zuerst auf nil zu setzen? Antwort: Es könnte ja sein,
dass die Liste leer ist. Dann ist nämlich
laenge von
vornherein -1 und die
(while)-Schleife wird überhaupt nicht
durchlaufen.
Wenn wir mit (defun) aus diesem Testausdruck eine Funktion machen würden,
dann könnten wir uns diese Zeile sparen und liste-ist-ok lokal anlegen.
Der Einsatz von
(member) erspart uns, die
(type)-Funktion
zweimal aufzurufen und die Aufrufe mit
(or) zu verknüpfen.
Als kleinen Schönheitsfehler könnte man noch bezeichnen, dass
liste-ist-ok jedesmal auf
T gesetzt wird, wenn das Element
eine Zahl ist. Vielleicht überlegen Sie sich ja eine bessere Lösung?
Die
(while)-Schleife wird übrigens abgebrochen, wenn ein
Element gefunden wird, das keine Zahl ist. Das erreichen wir dadurch,
dass wir
laenge auf -1 setzen.
Übungsaufgaben
-
Fassen Sie diesen Ausdruck ein wenig zusammen, so dass er
nur noch eine Zeile hat und das or überflüssig wird:
(or
(/= (type (nth laenge zahlenliste))'real)
(/= (type (nth laenge zahlenliste))'int)
)
-