Aus der Hilfe wissen wir, dass bei der Funktion set beide übergebenen Argumente evaluiert werden, doch wie kann man das nun sinvoll einsetzen?
Sehen wir uns die Funktion set einmal genauer an.
(set a "MeinWert") => Fehler: Fehlerhafter Argumenttyp: symbolp nil
Warum diese Fehlermeldung? Ganz einfach, die Variable a hat noch keinen Wert, ist also nil. set evaluiert aber diesen Wert und will somit den String "MeinWert" nil zuweisen.
(set 'a "MeinWert") => "MeinWert"
So klappt's. Durch Übergabe eines gequoteten Symbols wird nun dieses Symbol nicht mehr evaluiert. Das heißt nun, wir übergeben also "MeinWert" der Variablen a.
(set 'a 'MyVar)
(set a "Donald Duck")
Hier können wir nun gut erkennen, das durch das nicht gequotete Symbol nicht die Variable a den String "Donald Duck" erhält sondern MyVar!
Eingabe in der Visual LISP Konsole:
a => MYVAR
MYVAR => "Donald Duck"
Streng genommen gibt es eigentlich keine Variablen in AutoLISP. Es sind Bindungen an Symbole. Das q bei der Funktion setq steht für "Quote", setq quotet also das erste Argument genauso wie die Funktion defun. Solche Funktionen, die die Evaluation eines oder mehrerer Argumente unterdrücken, nennt man Special Forms.
Okay, nachdem wir nun einen Einblick haben, schauen wir mal, was wir damit nun Sinnvolles oder Unsinniges machen können.
1. Beispiel
Wir haben ein Art ini-Datei in der z.b. Folgendes steht:
'("Einrichtung" 7 "Arial" "Tisch" 1)
Wir laden diese Datei und binden diese Liste an eine Variable mit Namen MeineWerte:
(setq MeineWerte (load "Meine-Ini-Datei.ini"))
Wir wollen nun nicht mit dieser Liste arbeiten, sondern alle Werte sollen an Variablen gebunden werden. Dazu gibt es im Programm bereits eine vordefinierte Liste die die Symbolnamen enthält:
(setq MeineParameterListe '(Mein-Layer Meine-Farbe Mein-Textstil Der-einzufügende-Block Wie-oft-einfügen))
Jetzt brauchen wir nur noch die Funktion mapcar auf beide Listen loszulassen und der gewünschte Seiteneffekt entsteht: Die Bindung der Werte (MeineWerte) an die Symbole in der Liste MeineParameterListe ...
(mapcar 'set MeineParameterListe MeineWerte)
Eingabe in der Visual LISP Konsole
Mein-Layer => "Einrichtung"
Meine-Farbe => 7
Mein-Textstil => "Arial"
Der-einzufügende-Block => "Tisch"
Wie-oft-einf&¨gen => 1
2. Beispiel
Wir haben eine Assoziationsliste in der wir ständig irgendwelche Eintragungen vornehmen müssen. Diese Assoziationsliste könnte so aussehen:
(setq MeineParameterListe
'((layer . "Einrichtung")
(farbe . 7)
(linientyp . "Continuous")
(blockname . "Tisch")
(material . "Rotkernbuche")
(laenge . 2.10)
(breite . 1.00)
(hoehe . 0.76)
(h-anzahl . 2)
(v-anzahl . 3)
(h-abstand . 0.2)
(v-abstand . 0.3)
(text-an-block . 't)
)
)
Damit wir schnell einen, oder mehrer Einträge in dieser Liste ändern können, schreiben wir uns eine Funktion. Diese sollte neben der Parameterliste (#SourceAssocList) eine weitere Assoziationsliste (#ModifyAssocList) mit den zu ändernden Werten als Argument übergeben bekommen.
(defun :L-SubstList (#ModifyAssocList #SourceAssocList /)
(mapcar '(lambda (pair)
(cond ((assoc (car pair) #ModifyAssocList))
(pair)
)
)
#SourceAssocList
)
)
Mit dieser Funktion ändern wir nun in unserer Parameterliste die Farbe in Rot (1) und das Material in Eiche. Damit aber auch die Assoziationsliste geändert bleibt, also eine Bindung erfolgt, müssen wir hier ein setq ausführen.
(setq meineparameterliste
(:L-SubstList
((farbe . 1) (material . "Eiche"))
meineparameterliste
)
)
=> ((layer . "Einrichtung")
(farbe . 1)
(linientyp . "Continuous")
(blockname . "Tisch")
(material . "Eiche")
(laenge . 2.10)
(breite . 1.00)
(hoehe . 0.76)
(h-anzahl . 2)
(v-anzahl . 3)
(h-abstand . 0.2)
(v-abstand . 0.3)
(text-an-block . 't)
)
Aber warum muss man eigentlich immer ein setq angeben um eine Bindung an mein Symbol zu erhalten, ich bin doch schreibfaul und da ich sowieso immer in meinem Programm so verfahre, möchte ich mir diese ständigen setqs einsparen. Aber wie?
Basierend auf der ersten Funktion, erstellen wir einfach eine neue.
; Argumente
; #ModifyAssocList = Assoziationsliste mit den zu ändernden Werte
; #SourceAssocList = Die zu ändernde Assoziationsliste als gequotetes Symbol!
; coded by Rolf Wischnewski
(defun :L-SubstList! (#ModifyAssocList #SourceAssocList /)
(set #SourceAssocList
(mapcar '(lambda (pair)
(cond ((assoc (car pair) #ModifyAssocList))
(pair)
)
)
(eval #SourceAssocList)
)
)
)
Auf den ersten Blick sieht die neue Funktion wie unsere erste aus, doch bei genauerem betrachten erkennen wir, das ein set am Funktionsanfang ausgeführt wird und die zu ändernde Assoziationsliste (#SourceAssocList) evaluiert wird. Bei dieser Funktion wird also die zu ändernde Assoziationsliste (#SourceAssocList) nicht als Variable sondern als gequotetes Symbol übergeben. Set evaluiert das übergebene Argument #SourceAssocList, wogegen beim mapcar allerdings das gequotete Symbol mittel eval evaluiert werden muss.
Ändern wir unsere Parameterliste also nochmal, aber ohne ein setq !
(:L-SubstList!
'((layer . "Küche")
(farbe . 2)
(material . "Birke")
(laenge . 3.20)
(breite . 1.50)
(hoehe . 0.90)
(text-an-block . nil)
)
'meineparameterliste
)
=> ((layer . "Küche")
(farbe . 2)
(linientyp . "Continuous")
(blockname . "Tisch")
(material . "Birke")
(laenge . 3.20)
(breite . 1.50)
(hoehe . 0.90)
(h-anzahl . 2)
(v-anzahl . 3)
(h-abstand . 0.2)
(v-abstand . 0.3)
(text-an-block . nil)
)
Bingo! Der gewünschte Seiteneffekt ist da und die Funktion bindet die Parameter an unser Symbol. Einen Nachteil hat aber so eine Programmierung. Aufrufe von Funktionen mit Seiteneffekten müssen immer als m&¨gliche Fehlerquellen untersucht werden. Dadurch ist der Aufwand einer Korrektur bei eventuell falschen Varablenwerten höher als bei Programmen, die ohne diesen Seiteneffekt arbeiten.
Damit diese Funktionen auch als solche zu erkennen sind, sollte am Ende des Funktionsnamens ein Ausrufezeichen (!) stehen. Man nennt solche Funktionen auch destruktive Funktionen, was bedeutet es erfolgt eine Bindung als Seiteneffekt. Da unsere erste Funktion (:L-SubstList) nicht diese Eigenschaft besitzt muss explizit ein setq für eine Bindung ausgeführt werden.
3. Beispiel
Vor vielen Jahren quälte mich auch die Frage, wie man set sinnvoll einsetzen kann. Von Axel "mapcar" Strube-Zettler (* 29.01.1953, † 01.02.2006) lernte ich eine weitere interessante Möglichkeit kennen.
Um nicht ständig mit (cdr(assoc key (entget (car entitydata)))) auf die Objektliste zugreifen zu müssen, könnte man mithilfe einer foreach Schleife durch ein Assoziationsliste iterieren um uns ein paar Hilfsfunktionen zu erstellen. Bei Bedarf lässt sich diese Liste natürlich um weitere Einträge schnell und elegant erweitern.
(foreach task '((Text-> . 1)
(Layer-> . 8)
(Color-> . 62)
(BaseP-> . 10)
(EndP-> . 11)
)
(set
(car task)
(eval
(list
'lambda
(list '#ent)
(list 'cdr
(list 'assoc (cdr task) (list 'entget (list 'car '#ent)))
)
)
)
)
)
probieren wir es gleich mal aus...
(BaseP-> (entsel)) => (20.8819 19.6249 0.0)
(Color-> (entsel)) => 4
(Layer-> (entsel)) => "Achsen"
Genial einfach, einfach genial!
Mit freundlicher Genehmigung von Rolf Wischnewski. Originalbeitrag im Februar 2006, CADmaro.de