Manchmal gibt es Situationen in Programmen, in denen man die
Argumente für einen Funktionsaufruf bereits in einer Liste
zusammengfasst hat. Ein Grund dafür könnte sein, dass ihr
Programm in einen Eingabe- und einen Ausführungsteil geteilt
ist. Nehmen wir an, der Ausführungsteil zeichnet irgendein
Werkstück als Variantenkonstruktion und benötigt 25 verschiedene
Parameter.
In diesem Fall ist es sinnvoll, den Eingabeteil völlig abzuspalten.
Dieses Verfahren bietet den Vorteil, dass verschiedene Eingabemodule
den Ausführungsteil des Programms nutzen können: Benutzereingabe
über die Kommandozeile (lässt sich dann auch über Scripts ausführen),
Benutzereingabe über Dialogboxen (das empfinden viele Leute als
praktischer, lässt sich aber nicht von Script-Dateien ausführen)
und Auslesen der Zeichnungsparameter aus einer Steuerdatei sind die
drei häufigsten Arten.
Egal mit welcher der drei Möglichkeiten Sie zu den Eingaben
gekommen sind, müssten Sie nun den Ausführungsteil, der 25
Argumente erwartet, etwa so aufrufen:
(zeichne-variante
(nth 0 parameterliste)
(nth 1 parameterliste)
(nth 2 parameterliste)
(nth 3 parameterliste)
...
(nth 25 parameterliste)
)
Dieser reichlich sinnlosen Vergeudung Ihrer Schaffenskraft
können Sie mit
(apply) entgegentreten:
(apply zeichne-variante parameterliste)
tut es hier als Eingabe genausogut.
(apply) erwartet
als erstes Argument den Namen einer Funktion und als zweites
Argument eine Liste, deren Inhalt als Argumente an die Funktion
übergeben wird. Bei
(apply) wird also im Gegensatz
zu
(mapcar) die Funktion immer nur einmal ausgeführt.
Die Länge der Liste muss mit der Anzahl der erwarteten Argumente
der Funktion übereinstimmen. Ein paar Beispiele:
(apply '+ '(10 14 3 41 65))
=> 133
(apply '=> '(33 27 21 15 9 3))
=> T
(apply '=> '(33 21 27 15 9 3))
=> nil
In unseren Beispielen arbeiten wir hier immer mit Listen,
deren Inhalt bekannt ist. In der Praxis ist die Länge von
Listen jedoch meist unbekannt. Da die Länge der Liste, die
an
(apply ...) übergeben wird, mit der Anzahl der
Argumente der anzuwendenden Funktion übereinstimmen muss,
ist es im Zusammenhang mit Listen unbekannter Länge eigentlich
nur möglich, Funktionen mit einer variablen Anzahl von
Argumenten zu übergeben. Da man diese nicht selbst definieren
kann, bleiben in diesem Fall nur die eingebauten Funktionen
wie
(+ ...) usw.
Ein weiteres Beispiel: Sie haben wieder eine Punkteliste
pl und möchten die arithmetischen Mittel aller X-Werte
und aller Y-Werte berechnen. Das Ergebnis des Ausdrucks soll
eine Liste mit den beiden Mittelwerten sein. Das könnte dann
etwa so aussehen:
(list
(/(apply '+ (mapcar'car pl))(length pl))
(/(apply '+ (mapcar'cadr pl))(length pl))
)
Und wenn's nun auch noch 3D sein soll? Es wäre natürlich
einfach, auch noch einen dritten Ausdruck für die Z-Werte
einzusetzen.
(list
(/(apply '+ (mapcar'car pl))(length pl))
(/(apply '+ (mapcar'cadr pl))(length pl))
(/(apply '+ (mapcar'caddr pl))(length pl))
)
Aber soll es denn wirklich in derartige Schreibarbeit
ausarten? Wir turnen also doch lieber einen doppelten
(mapcar) mit eingesprungenem
(lambda):
(mapcar
'(lambda(arg)
(/(apply '+(mapcar arg pliste))(length pliste))
)
'(car cadr caddr)
)
Lassen Sie sich Zeit, diese Konstruktion nachzuvollziehen.
Sie ist ja auch wirklich nicht ganz einfach. Experimentieren
Sie nach Möglichkeit eine Weile mit ähnlichen Ausdrücken und
versuchen Sie ein wenig Sicherheit im Umgang mit solchen
Ausdrücken zu entwickeln!
Und nun zur Funktion
(foreach). Es handelt sich hier
zwar auch um eine Funktion zur Bearbeitung von Listen, ihr
Funktionscharakter gleicht aber mehr den Funktionen zur
Schleifensteuerung.
(foreach) erwartet mindestens
drei Argumente: Zuerst einen frei wählbaren Symbolnamen, dann
eine Liste, und schliesslich mindestens einen zu evaluierenden
Ausdruck. Das kann im Beispiel so aussehen:
(foreach datei
'("progr1" "progr3" "progr7" "progr9")
(load datei)
)
Dieser Ausdruck ist in der Wirkung identisch mit
(load "progr1")
(load "progr3")
(load "progr7")
(load "progr9")
(foreach) ordnet nacheinander dem Symbol
datei
die Elemente der Liste zu und evaluiert dann jeweils den in
Argument 3 gegebenen Ausdruck. Beachten Sie, dass das erste
und das dritte Argument nicht quotiert wurden.
(foreach)
verhindert die Evaluation von Argument 1 und Argument 3
automatisch.
Die Rückgabe von
(foreach) ist identisch mit der Rückgabe
der letzten durchgeführten Evaluation von Argument 3. Daraus
schliessen wir, dass auch bei
(foreach) der Haupteffekt
Nebensache und der Nebeneffekt die Hauptsache ist. In der
Praxis wird der Haupteffekt von
(foreach) eigentlich nie
verwertet.
Wir werden gleich darauf kommen, warum man
(foreach)
auch als Schleifensteuerung betrachten kann. Zunächst soll
erst einmal die Nähe zu
(mapcar) untersucht werden:
(mapcar
'(lambda(datei / )
(load datei)
)
'("progr1" "progr3" "progr7" "progr9")
)
Mit
(mapcar) und
(lambda) lässt sich also das
Selbe bewerkstelligen, allerdings mit etwas mehr Aufwand. Der
Unterschied: Die
(mapcar)-Konstruktion gibt eine Liste
mit den Ergebnissen aller
(load)-Aufrufe zurück. Die
Version mit
(foreach) gibt nur das Ergebnis des letzten
durchgeführten Aufrufs von
(load ...) zurück.
Wir können das so zusammenfassen: Man sollte eine
(mapcar)-Konstruktion verwenden, wenn man am Effekt
interessiert ist. Ist nur der Neben- bzw. Seiteneffekt von
Interesse, ist die Anwendung von
(foreach ...)
ein wenig einfacher.
(foreach) ist neben
(lambda) die einzige in
AutoLisp eingebaute Funktion, die für ein Symbol einen Namensraum
erzeugt. Das bedeutet, dass das Symbol
datei nur innerhalb
der
(foreach ...)-Anweisung definiert ist. Betrachten wir
unser Beispiel noch einmal als (nicht korrekte) Funktionsdefinition:
(defun dateien-laden(dateiliste / datei)
(foreach datei dateiliste
(load datei)
)
)
Dieser Code sieht korrekt aus und funktioniert hunderprozentig,
was soll also falsch daran sein? Wir haben hier zwei Variablen
mit dem Namen
datei erzeugt! Der Eintrag von
datei
als lokale Variable ist völlig überflüssig!
Ein weiteres Beispiel: Eine Liste enthält einen in Einzelteile
zerlegten vollständigen Dateinamen. Es soll eine Funktion
definiert werden, die solche zerlegten Dateinamen wieder
zusammensetzt:
(defun name-zusammensetzen(teile / ganz)
(setq ganzname "")
(foreach teil
(reverse(cdr(reverse teile)))
(setq ganz(strcat ganz teil "\\"))
)
(setq ganz
(strcat ganz "."(last teile))
)
)
(setq name-zum-testen
'("c:" "acad" "lisp" "progr" "letztes" "lsp"))
(name-zusammensetzen name-zum-testen)
=> "c:\acad\lisp\progr\letztes.lsp"
Sollten Sie hier Schwierigkeiten mit dem Backslash haben, dann
lesen Sie bitte noch einmal bei der Funktion
(load) nach!
Wir greifen nun noch einmal eines unserer Beispiele aus einem
vorigen Kapitel wieder auf. Es ging darum, in einer Liste mit
Punkten doppelt vorkommende Koordinaten zu finden. Noch einmal
der Stand der Dinge:
(defun doppelter-ywert(ywert punkte)
(member ywert
(cdr(member ywert(mapcar 'cadr punkte)))
)
)
Diese Funktion war immerhin schon in der Lage, einen zahlenmässig
bestimmten y-Wert zu suchen und anzuzeigen, ober er mehr als
einmal vorkommt. Erweitern wir die Funktion jetzt mit
(foreach),
sodass sie irgendeinen doppelten y-Wert findet:
(defun doppelter-ywert(liste / ywert ergebnis)
(foreach ywert(mapcar 'cadr liste)
(if
(member ywert
(cdr(member ywert(mapcar 'cadr liste)))
)
(setq ergebnis(append ergebnis ywert))
)
)
ergebnis
)
Die neue Version unserer Funktion stellt schon einen qualitativen
Schritt nach vorn dar. Sie erfüllt Ihren Zweck, und sie gibt
statt eines blossen Listenrestes eine Liste zurück, in der alle
mehrfachen Vorkommen von Y-Werten erfasst sind. Auch diese Liste
ist als logisches 'wahr' interpretierbar, wenn man an weiteren
Informationen interessiert ist, kann man sie auswerten.
Aber noch einmal zurück zu
(foreach) und seiner Nähe zur
Schleifensteuerung. Es ist in der Tat nicht aufwändig, eine
(foreach)-Anweisung durch eine
(repeat)-Schleife zu
ersetzen. Wir müssen nur etwas umstellen:
(foreach element liste ... )
; wird zu:
(setq i 0)
(repeat(length liste)
(setq element(nth i liste)i(1+ i))
...
)
Anstelle der Pünktchen kann man den zu evaluierenden Ausdruck
(oder auch mehrere) einsetzen und wird mit beiden Konstruktionen
zum gleichen Ergebnis kommen.
(foreach) ist also eine
Funktion, die zwar praktisch und übersichtlich anzuwenden ist,
aber im Grunde - wie wir z.B. schon bei
(setq) erörtert
haben - nicht unbedingt notwendig ist.