Bisher haben wir nur einzelne Ausdrücke zum Evaluieren an
AutoLISP geleitet. Wenn wir aber daran denken, richtige
Programme zu schreiben, mit denen wir Problemstellungen in
AutoCAD bearbeiten wollen, dann müssen die Programme
- als neue (selbstdefinierte) Funktionen aufrufbar sein
- in der Lage sein, auf unterschiedliche Situationen unterschiedlich zu reagieren
- in Dateien abgelegt werden können
- bei Bedarf aufrufbar sein
Was bringen uns Funktionen? Funktionen fassen Teile des
Programmcodes zusammen und machen ihn mehrfach verwendbar. Wenn
wir absehen können, dass ein bestimmter Ablauf mehrmals im Programm
vorkommen wird, dann sollten wir eine Funktion dafür schreiben. Das
erspart Schreibarbeit und auch Speicherplatz, da der entsprechende
Code nur einmal im Speicher vorhanden ist und nicht mehrfach.
Ein weiterer Vorteil ist, dass die Anzahl der Variablen im
Hauptprogramm sehr stark reduziert wird, da die meisten Variablen
in den Funktionen verschwinden und dort lokal sind (dieser Begriff
wird im Verlauf dieses Kapitels noch erläutert).
Im Laufe der Zeit wird man dann auch noch feststellen, dass es
sehr nützlich sein kann, Funktionen anzulegen, die nicht mehr nur
innerhalb eines Programms verwendet werden, sondern aus allen
möglichen verschiedenen Programmen heraus aufgerufen werden können.
Eine solche Sammlung von allgemein nutzbaren Funktionen bezeichnet
man als Funktionsbibliothek.
Beginnen wir damit, unser bisher erarbeitetes Wissen als neue
Funktionen im Speicher abzulegen und dadurch dafür zu sorgen,
dass wir sie jederzeit wieder aufrufen und benutzen können.
Die entsprechende AutoLISP-Funktion heisst
(defun ...),
was in etwa mit 'definiere Funktion' zu übersetzen ist. Sie ist
eine von den Funktionen, die eine beliebige Anzahl von Argumenten
erhalten können, das Minimum an Argumenten ist allerdings drei.
Sie gehört aber auch zu der Gruppe von Funktionen, die
Nebeneffekte haben. Ihre Rückgabe, der Haupteffekt, besteht
lediglich aus dem Namen der definierten Funktion.
Die Anwendung von
(defun ...) gleicht in dieser Hinsicht
also der Anwendung von
(set) und
(setq), die wir
ja auch wegen ihrer Nebeneffekte nutzen und den Haupteffekt
meistens verpuffen lassen. Eine weitere Eigenschaft von
(defun) ist, dass alle Argumente nicht evaluiert werden.
Sie wie
(setq) die Evaluation des ersten Argumentes
verhindert, wird sie von
(defun) bei allen Argumenten
verhindert.
Das erste Argument von
(defun) gibt den Namen der neuen Funktion
an, d.h. das Symbol, an das der Funktionscode gebunden wird.
Das zweite Argument von
(defun) hat eine besondere Bedeutung
und muss auf jeden Fall eine Liste sein. Diese Liste kann unter
Umständen leer sein, sie muss aber immer übergeben werden. Diese
Liste heisst in AutoLISP 'Formale Argumentenliste'. In ihr werden
die Namen der Argumente für die neue Funktion eingetragen.
Ein Beispiel:
Wir wollen eine Funktion schreiben, die eine Zahl quadriert. Der
Name soll
(sqr) sein. AutoLISP selbst stellt diese Funktion
nicht zur Verfügung, quadrieren können wir also nur mit
(* ...) und
(expt ...). Unsere neue Funktion
(sqr) hat also durchaus praktischen Nutzen. Das Argument
für
(sqr) nennen wir einfach
zahl. Und so sieht
die Funktionsdefinition aus:
(defun sqr (zahl) (* zahl zahl))
Nachdem wir diesen Ausdruck eingegeben haben, können wir unsere
neue Funktion gleich ausprobieren:
(sqr 8) => 64
(sqr -1) => 1
(sqr 1.5) => 2.25
Das sieht doch schon sehr gut aus, nicht wahr? Wir machen noch einen
Test, um festzustellen, ob die Funktion wirklich korrekt arbeitet. Die
Überlegung bei diesem Test ist einfach die, daá sich Quadrieren und
Wurzelziehen gegenseitig aufheben.
(sqr(sqrt 153.75)) -> 153.75
Es überrscht nicht, dass unsere Funktion des Test bravourös besteht.
Wir wollen jetzt etwas genauer untersuchen, wie der Aufruf
von
(defun) evaluiert wurde und was er bewirkte.
(defun) wurde mit drei Argumenten aufgerufen, also der
minimal möglichen Anzahl von Argumenten. Keines der drei Argumente
wurde quotiert,
(defun) macht das automatisch für uns. Das
erste Argument legte den Namen sqr für unsere neue Funktion fest.
Das zweite Argument - die formale Argumentenliste - enthielt nur
ein Element, nämlich das Symbol zahl. Das dritte Element stellte
den Programmcode zur Verfügung, der hier sehr kurz und übersichtlich
war. Wird der Programmcode länger, dann werden mehrere oder
viele Ausdrücke eingesetzt und die Anzahl der Argumente für
(defun) steigt.
Aufrufe von
(defun) mit weniger als drei Argumenten erzeugen
einen Fehler. Das ist logisch, da eine neu definierte Funktion
sowohl einen Namen, eine Argumentenliste als auch mindestens
eine Code-Anweisung haben muss. Eine Funktion ohne Namen wäre
nicht aufzurufen, und eine Funktion ohne Code würde niemals
etwas bewirken. Die Argumentenliste ist - wie bereits gesagt -
einfach technisch unabdingbar.
Bis einschliesslich zur AutoCAD-Version 14 ist eine Funktion in
AutoLISP eine Liste wie jede andere auch. Wir können also aus
AutoCAD heraus unsere neue Funktion inspizieren, und das
Ergebnis sollte uns nicht weiter überraschen:
!sqr => ((zahl) (* zahl zahl))
Ab AutoCAD 2000 sieht die Sache etwas anders aus. Wie wir ja
bereits wissen, wird der Funktionscode nicht mehr wie in den
älteren Versionen als lesbare Liste abgelegt, sondern er
verschwindet hinter einer Kennzeichnung:
!sqr => #<SUBR @01f112f8 SQR>
Was passiert nun (in allen AutoCAD-Versionen), wenn wir die
Funktion aufrufen? Das ist einfach erklärt: Wenn wir
(sqr 5)
eingeben, wird der Wert 5 vor Beginn der Evaluation an das
Symbol
zahl gebunden und der an das Symbol
sqr
gebundene Programmcode evaluiert.
Vielleicht ist Ihnen aufgefallen, dass wir gar nicht definiert
haben, was die Rückgabe unserer Funktion sein soll. Das brauchen
wir auch gar nicht. Rückgabe einer Funktion ist immer der
Haupteffekt des letzten in der Funktion evaluierten Ausdrucks.
Wir könnten nun auf die Idee kommen, das hier gesagte dadurch
zu überprüfen, dass wir
!zahl auf der Kommandozeile eingeben.
Die Rückgabe ist aber
nil, und nicht etwa, wie wir erwarten
könnten, 5. Woran liegt das? zahl ist ein Symbol, das nur innerhalb
des Funktionsablaufes von
(sqr) eine Wertbindung hat.
zahl ist eine lokale Variable innerhalb von
(sqr) und
kann von ausserhalb weder manipuliert noch abgefragt werden. Sie
werden sich mit zwei weiteren Begriffen anfreunden müssen:
Sichtbarkeit und Lebensdauer von Variablen.
Die Sichtbarkeit von Zahl ist auf die Funktion
(sqr)
beschränkt. Ihre Lebensdauer ist identisch mit dem Zeitablauf
der Funktion
(sqr). Wir machen ein kleines Experiment und
geben die folgenden Ausdrücke ein:
(setq zahl 12) => 12
(sqr 5) => 25
!zahl => 12
Die Antworten auf unsere Eingaben lassen nur einen Schluss zu:
Die Variable
zahl, der wir hier den Wert 12 zugeordnet haben,
kann nur eine ganz andere Variable sein als zahl in unserer
Funktion
(sqr). Die neue Variable zahl (mit dem Wert 12) nennt
man eine globale Variable, ihre Lebensdauer endet erst dann,
wenn wir sie auf nil setzen oder eine andere Zeichnung laden.
Wie verhält es sich aber mit ihrer Sichtbarkeit? Sie kann doch
wohl innerhalb von
(sqr) nicht sichtbar sein, denn dann gäbe
es dort zwei Variablen mit dem gleichen Namen. Das kann aber
niemals sein, denn der Zugriff auf solche Variablen wäre dann
kaum oder gar nicht regelbar.
Die Argumente einer Funktion sind also lokal zu ihr, d.h. sie
sind nur innerhalb dieser Funktion sichtbar. Wir können aber
auch andere Symbole lokal machen. Dazu muss man nur folgendes
wissen: Nach den Argumenten setzt man das Symbol
/ in die
Formale-Argumente-Liste und kann dann nach dem Schrägstrich
weitere Symbole angeben, die lokal sein sollen. Das kann
z.B. so aussehen:
(defun neue-funktion(arg1 arg2 / lvar1 lvar2) ... )
Alle vier Symbole werden dadurch lokal.
(neue-funktion) hat
also zwei Argumente und zwei weitere lokale Variablen, die z.B.
benutzt werden können, um Werte zwischenzuspeichern usw.
Es ist eine Eigentümlichkeit von AutoLISP, dass die
Argumentenliste durch den Schrägstrich geteilt wird. Immerhin
ist der Schrägstrich ja das Symbol, an das der Funktionscode
der Division gebunden ist. Es wäre wahrscheinlich manches
etwas einfacher bzw. verständlicher, wenn man die beiden
Listen einfach geteilt hätte und die Definition des letzten
Beispiels so aussehen würde:
(defun neue-funktion(arg1 arg2)(lvar1 lvar2) ... )
Es ist aber nun mal nicht so gemacht worden, und damit müssen
wir leben. Ein häufig autretender Anfängerfehler ist, dass
vergessen wird, rechts oder links des Schrägstriches
Leerstellen zu lassen. Bei Klammern z.B. muss man keine
Leerstellen lassen, da Klammern Operatoren sind. Der
Schrägstrich jedoch ist ein Symbol wie jedes andere auch, und
daher muss er mit Leerstellen von anderen Symbolen abgegrenzt
werden. Lassen Sie uns die folgende falsche
Eingabe untersuchen:
(defun neue-funktion(arg1 arg2 /lvar1 lvar2) ... )
Hier wird eine Funktion definiert, die 4 Argumente hat, jedoch
keine weiteren lokalen Symbole. Das kommt daher, dass
/lvar1
ein genauso gültiger Name für ein Symbol ist wie
arg1 oder
lvar2. Es hindert sie auch niemand daran, ein Symbol
/// oder
pi/2 zu nennen. Nicht gültig sind, wie bereits in einem der
vorigen Kapitel erwähnt, lediglich die Zeichen, die für die
Operatoren reserviert sind: ()"'.; und die Leerstelle.
Wenn Sie nach dieser Definition
(neue-funktion) mit zwei
Argumenten aufrufen, werden Sie jedenfalls die Fehlermeldung
'Falsche Anzahl Argumente' erhalten.
Übungsaufgaben
-
Welche Argumente von (defun) werden automatisch quotiert?
-
Welche der folgenden Aussagen sind richtig?
-
Funktionen werden definiert, wenn abzusehen ist, dass der
selbe Vorgang mehrmals in einem Programm ablaufen soll
-
Funktionsdefinitionen sparen Schreibarbeit und Speicherplatz
-
Selbstdefinierte Funktionen waren bis AutoCAD 14 ganz
normale Listen, sind es aber seit AutoCAD 2000 nicht mehr
-
Die Rückgabe einer Funktion ist immer der Nebeneffekt des
letzten evaluierten Ausdrucks
-
Wenn man eine Funktion ohne Argumente definiert, kann die
Formale-Argumenten-Liste weggelassen werden
-
Definieren Sie folgende Funktionen:
-
Eine Funktion, die ein Argument erhält und
das Argument immer unverändert zurückgibt
-
Eine Funktion ohne Argumente, die immer nil zurückgibt
-
Eine Funktion, die nur den Nachkomma-Anteil einer
Realzahl zurückgibt
-
Eine Funktion, die zwei Listen als Argumente erhält und
eine Liste aus den beiden jeweils ersten Elementen
zurückgibt
-
Eine Funktion, die eine Liste ohne das letzte Element
zurückgibt
-
Eine Funktion, die das vorletzte Element einer Liste
zurückgibt