Closure
aus LPCWiki, der freien Wissensdatenbank
Closures für Einsteiger
Was ist eine Closure überhaupt?
Die einfachsten Closures sehen so aus:
#'func
Diese Closure liefert -- wenn ausgeführt -- den Rückgabewert von
func()
Dabei kann func() sowohl eine efun [1], eine lfun [2] oder sogar ein Operator (z.B.: +, *, += usw.) sein.
Source(s): Closure (http://www.downloadranking.com)
Und was bringt mir das?
Nun, zunächst einmal gar nichts. Allerdings, man kann es benutzen, um variable Details einzubauen:
create() {
::create();
[...]
AddDetail("wand",#'unt_wand);
[...]
}
string unt_wand() {
if( this_player()->QueryProp(P_RACE) == "zwerg" )
return "Eine solide Wand aus Kanraxschem Marmor (der dunkleren Sorte).\n";
else
return "Eine solide Wand aus dunklem Marmor.\n";
}
Damit kriegen die Zwerge einen anderen Text als die anderen Spieler, wenn sie sich diesen Detail anschauen. Dies geht natürlich auch an anderen Stellen, wo Texte ausgegeben werden: bei P_INT_LONG und P_INT_SHORT, allen Arten von Details (ReadDetails, Smell, ...), AddInfo, AddRoomMessage usw.
Bislang habe ich process_string() verwendet, etwa so:
AddDetail("tuer","Eine @@zustand@@ Tuer.\n")
Source(s): Closure (http://www.downloadranking.com)
Wie geht das mit Closures?
Nun, es gibt zwei Möglichkeiten. Erstens, wir könnten wieder die vorherige Methode benutzen:
create() {
::create();
[...]
AddDetail("tuer",#'tuer_zustand);
[...]
}
string tuer_zustand() {
return "Eine "+zustand()+" Tuer.\n";
}
Die andere Methode ist etwas komplizierter, benötigt allerdings keine zusätzliche Funktion.
create() {
::create();
[...]
AddDetail("tuer",lambda(0,
({#'sprintf, "Eine %s Tuer.\n",
({#'zustand})
}) ) );
[...]
}
Der komplizierte Teil ist die Closure:
lambda(0, ({#'sprintf, "Eine %s Tuer.\n", ({#'zustand}) }) );
Der erste Argument von lambda() ist ein Array (eine Tabelle) mit Werten, die als Argumente für den zweiten Argument von lambda(), der eigentlichen Closure, verwendet werden. In unserem Fall haben wir an dieser Stelle keine Argumente, und die Closure entspricht der Funktion:
string _irgend_eine_funktion() {
return sprintf("Eine %s Tuer.\n", zustand());
}
Die Closure wird von Innen nach Aussen abgearbeitet; zuerst wird die Closure
- 'zustand aufgerufen (was dem Aufruf der Funktion zustand() entspricht).
Anschliessend wird die Closure #'sprintf aufgerufen, und zwar mit den Argumenten "Eine %s Tuer.\n" und ({#'zustand}) (dem Rückgabewert der Funktion zustand()). Das Ergebnis des letzten Funktionsaufrufs wird dann als Ergebnis der Closure selbst interpretiert (deswegen brauchen wir auch kein #'return :-)
Source(s): Closure (http://www.downloadranking.com)
Was kann ich noch damit machen?
Alles Mögliche, hier kommt ein ausführliches Beispiel: [3]:
Nehmen wir mal an, wir wollen eine Funktion schreiben, die an alle Spieler in einem Enviroment (einer Umgebung -- im Normalfall ein Raum) einen Text mit Hilfe von tell_object() ausgibt. Jeder normale Mensch würde da sowas schreiben:
tell_room(env,text); // *grins*
Da gibt es nur ein Problem: Der Text "text" wird auch an Nicht-Spieler verschickt. Daher müssen wir eine andere Lösung suchen:
object *obs;
obs = filter_array( all_inventory(env), #'interactive );
map_array( obs, #'tell_func, text );
[...]
int tell_func( object ob, string text ) {
tell_object( ob, text );
}
Die Funktionen tun folgendes: Zuerst wird der Inhalt des Enviromants "env" geholt (all_inventory(env)). Dieses Array (diese Tabelle) mit objects (Objekten) als Inhalt wird nun durch die efun [1] interactive() gemappt, soll heissen, fuer jeden Element aus dem Array wird die Funktion interactive(Element) aufgerufen. Wenn diese Funktion den Wert 1 liefert (Also, wenn der Element ein Spieler ist), dann wird dieser Element im Array behalten, ansonsten wird es verworfen. (Hier kann man auch recht gut sehen, wie praktisch die Benutzung der Closures sein kann :-)
In der Variablen "obs" haben wir ein Array, in welchem nur die Spieler in diesem Enviroment stehen. Mit der Funktion map_array() koennen wir nun fuer jeden einzelnen Element die Funktion tell_func() aufrufen, die als ersten Argument den Element und als zweiten Argument den Inhalt der Variablen "text" bekommt. Hier sehen wir wieder eine Verwendungsmöglichkeit für einfache Closures...
Die Funktion tell_func() macht nichts weiter, als den Text an den jeweiligen Objekt (also, in unserem Fall, an den Spieler) auszugeben.
Und jetzt versuchen wir dasselbe mit Closures :-)
map_array( filter_array( all_inventory(env), #'interactive ),
lambda( ({ 'pl, 'text }),
({ #'tell_object, 'pl, 'text }) ),
text );
Nun, das war die einfache Version. Wir haben den Funktionsaufruf für die Funktion tell_func() durch eine Closure ersetzt. Das Neue dabei ist das Array
({ 'pl, 'text })
als erster Argument von lambda(). Dieses Array bedeutet ganz einfach, daß diese Closure 2 Argumente hat, und daß sie innerhalb der Closure mit 'pl bzw. 'text bezeichnet werden.
Die eigentliche Closure,
({ #'tell_object, 'pl, 'text })
ist dagegen recht einfach: Es wird die Funktion tell_object( pl, text ) aufgerufen.
Für die "Fanatiker" kommt nun die Ganz-Closure-Variante:
closure c;
c = lambda( ({ 'env, 'message }),
({ #'map_array,
({ #'filter_array,
({ #'all_inventory, 'env }),
#'interactive }),
({ #'lambda,
'({ 'pl, 'text }),
'({ #'tell_object, 'pl, 'text }) }),
'message }) );
Nett, nicht wahr? :-)
Es gibt hier ein paar neue Dinge. Erstens, #'interactive steht nicht in Klammern -- das kommt daher, dass wir die Closure selbst brauchen, und nicht, wie bisher üblich, ihren Wert.
Die zweite Überraschung ist die erneute Verwendung der lambda()-Funktion -- diesmal im Inneren einer Closure. Auch an dieser Stelle brauchen wir nunmal eine Closure, und zwar die, die uns die innere lambda() liefert. Die Funktion selbst braucht zwei Arrays als Argumente, und, um zu verhindern, daß diese wie gewöhnlich interpretiert werden (was einen Fehler verursachen würde), müssen wir vor jedem ein Apostroph-Zeichen setzen:
'({ 'pl, 'text }),
'({ #'tell_object, 'pl, 'text })
Damit bleiben die Arrays als Arrays erhalten, und wir haben ein gutes Beispiel für verschachtelte Closures :-)
Wozu der Streß? Nun, jetzt können wir die Closure ähnlich einer Funktion benutzen:
funcall(c, find_object("/players/akjosch/workroom"), "Hallo!\n" );
tut genau das, was wir uns davon erhoffen: an alle anwesenden Spieler (allerdings NICHT an NPCs oder andere Objekte) im Raum "/players/akjosch/workroom" wird der Text "Hallo!\n" ausgegeben...
Die geschlechtspezifischen Details sind ja nicht mehr da, kann man sowas auch mit Closures machen?
Ja, man kann. Zuerst definiert man eine Funktion, die folgendes macht:
string _get_detail(string text_md, string text_mn, string text_fd, string text_fn) {
int gen;
gen = this_player()->QueryProp(P_GENDER);
if( gen == MALE )
if( is_day() )
return text_md;
else
return text_mn;
else
if( is_day() )
return text_fd;
else
return text_fn;
}
Also, der Funktion werden 4 Zeichenketten übergeben:
* text_md: Text für die Männer, am Tag sichtbar. * text_mn: Text für die Männer, in der Nacht sichtbar. * text_fd: Text für die Frauen, am Tag sichtbar. * text_fn: Text für die Frauen, in der Nacht sichtbar.
Statt eines Textes kann man auch eine 0 übergeben, das generiert den "Sowas siehst Du nicht!\n"-Text.
Und so benutzt man es: [4]
create() {
[...]
AddDetail(({"lachen","laecheln"}), lambda(0, ({#'_get_detail,
"Du bekommst ganz weiche Knie, als Du Dir dieses Laecheln naeher "
"anschaust. Was fuer eine Frau...\n",
0,
"Die Statue laechelt in einer Art, die bei braven Ehefrauen "
"im allgemeinen dazu fuehrt, dass sie ihren Gatten an den Arm nehmen "
"und schnell aus der Reichweite einer auf diese bewusste Art laechelnden "
"Frau bringen.\n",
0}) ) );
[...]
}
Voilà!
Wird nun dieses Detail untersucht, so ruft die Closure die Funktion _get_detail() mit den 4 angegebenen Argumenten auf. Der Vorteil dieser Methode ist, daß man die Funktion _get_detail() mehrfach an verschiedenen Stellen verwenden kann.
Source(s): Closure (http://www.downloadranking.com)
[1] Eine efun ist eine globale Funktion. Diese sind meistens im Driver oder in der Datei /secure/simul_efun.c definiert und können von jeder Datei aus jederzeit aufgerufen werden.
[2] Eine lfun ist eine lokale Funktion. Diese ist meistens in der Datei selbst oder in einer der durch "inherit" eingebundenen Dateien enthalten und damit nur auf einen Teil -- oft sogar nur auf eine einzige -- der im Mud vorhandenen Dateien beschränkt.
[3] Idee von Khidar.
[4] Der Beispiel stammt aus einem von Tigerauges Räumen, nämlich /d/gareth/tigerauge/rooms/monumnt3.c.
Martin Sojka (maso@cscip.uni-sb.de)
Closures für Fortgeschrittene
Author: Avatar@Avalon
Folgender Text stammt im Original von Ironlord, und wurde von Avatar korrigiert und erweitert.
Source(s): Closure (http://www.downloadranking.com)
Wann muss man Closures verwenden
- Wenn ein Programm einem (gecloneten) Objekt dynamische Meldungen
uebergeben soll (siehe auch /doc/funktionsweisen/messages).
- Wenn ein Funktionsablauf von einem Parser "zusammengestellt" werden
soll (siehe auch /secure/simul_efun/deklin.c)
- Wenn Funktionen in ein Objekt eingebunden werden muessen (koennen
nur privilegierte Objekte, siehe /secure/master/create_wiz.inc)
Source(s): Closure (http://www.downloadranking.com)
Wann kann man Closures verwenden
- Bei Filter- und Sortieralgorithmen, da sie den Code kompakt halten
(siehe update_level() in /i/player/login.c oder query_active_jobs()
in /apps/erqd.c).
- Bei Funktionen wie add_command() oder set_long(), wenn bei einem
Funktionsaufruf auch Parameter wie this_player() uebergeben werden
muessen (siehe auch /d/Ela*/ava*/los*/jer*/gilde/npc/esala.c)
(veraltet! Anm. --Odin 19:08, 7. Sep 2005 (CEST))
Source(s): Closure (http://www.downloadranking.com)
Wann sollte man Closures lieber nicht verwenden
- Immer dann, wenn die Lesbarkeit des Source-Textes darunter sehr leiden
wuerde, sich der Aufbau der Funktion eh nie aendert, und die Closure
lediglich in einem einzigen Objekt benutzt wird (Negativbeispiel:
/d/Elandor/map.c).
Source(s): Closure (http://www.downloadranking.com)
Um Missverstaendnisse von vornherein auszuschliessen
- Closures sind nicht die Assemblersprache des LPC
(will heissen, das sie nur dort schneller sind wo sie wirklich
gebraucht werden)
- es hat keinen Sinn alle Funktionen eines Objektes in Closures
umzuwandeln
Folgende Beispiele sollen zeigen, wie man mit Closures arbeitet:
Die Closure und das VItem
In einem VItem kann man z.B. dem "long" einen festen String (Text) zuweisen. Was ist aber nun wenn sich dieser Text aendern soll, zum Beispiel, wenn eine Kerze "erzeugt" werden soll, die im brennenden Zustand anders aussieht als im erloschenen. Kein Problem, man ersetzt den String einfach durch eine Closure, und laesst von dieser den String waehrend der Laufzeit erzeugen.
Beispiel:
//Dies ist keine Closure, sondern nur die von Ihr aufgerufene Funktion
string an_oder_aus() {
if (licht_an)
return "Die Kerze flackert im Wind.\n";
else
return "Die Kerze ist leider aus, zuende sie doch an.\n";
}
void create() {
[...]
add_v_item( ([
"name": "kerze",
"gender": "weiblich",
"long" : #'an_oder_aus //dies ist die Closure
]) );
Funktionsweise: Beim Betrachten der Kerze wird zunaechst die Funktion 'an_oder_aus' aufgerufen, diese schaut nach ob 'licht_an' gesetzt ist und gibt den entsprechenden Text an "long", danach wird der Text an den Betrachter ausgegeben.
Dies ist eine recht simple Closure, die lediglich eine Funktion aufruft, die sich innerhalb des Objektes befindet. Solche zusaetzlichen Funktionen kann man sich auch ersparen, indem man eine Lambda-Closure kreiiert.
Lambda-Closures
Grundlagen und Grundfunktionen
Erstmal ein kleiner Einblick, wie obiges Beispiel mit Hilfe einer Lambda-Closure geloest werden kann:
closure an_oder_aus;
int licht_an_aus=1;
int licht_an; // Globale Variable, die geaendert wird, wenn jemand die
// Kerze auspustet
int query_licht_an() { return licht_an; }
// Hier erfaehrt die Closure den Wert der globalen Variable
void create()
{
licht_an=1; // Defaultmaessig ist die Kerze an
add_v_item( ([
"name": "kerze",
"gender": "weiblich",
"long": lambda(0,
({ #'?,
({ #'query_licht_an }),
"Die Kerze flackert im Wind.\n",
"Die Kerze ist aus.\n"
}) )
]) );
}
Wow, ganz schoen viele neue Sachen auf einmal. Ok, gehen wir mal alles der Reihe nach durch:
Die lambda-Funktion
closure lambda(mixed *Variable,mixed *Closure);
Mit Hilfe der lambda-Funktion wird die Closure erzeugt. Diese Funktion benoetigt zwei Uebergabeparameter. Das erste Feld enthaelt (lokalen) Variablen, welche spaeter per funcall() oder apply() uebergeben werden, und das zweite Feld ist der "Source-Text" der Closure. Werden keine lokalen Variablen benoetigt, kann man als 1. Argument auch eine 0 uebergeben, wie im Beispiel zu sehen.
Der Aufruf: #'?
Er verhaelt sich genauso wie eine if-Anweisung.
Statt if( <check> ) <result1> else <result2>
| | | |
| | | |
schreibt man #'?, ({ #' ... }), ({ #' ... }), ({ #' ... })
| | | |
| | | |
oder auch #'?, ({ #'query_x }), "Ergebnis ungleich 0", "Ergebnis 0"
Merke: Die ({ })-Klammern setzt man immer dann, wenn in einem "Feld" ein separater Funktionsaufruf erfolgen soll. Ist dies nicht der Fall, kann man die Klammern auch weglassen (siehe Beispiel).
Closure-Operatoren
Man sieht, das Closures in Feldern abgelegt werden. Man kann es sogar noch einfacher formulieren, jeder neue Befehl einer Closure wird in einem Feld abgelegt. Befehle sind in diesem Falle aber auch Operatoren wie
+= -= < > != ? (mini-if-befehl) usw.
und so gibt es fuer jeden dieser 'Befehle' einen Closureoperator:
#'+= #'-= #'< #'> #'!= #'? usw.
Allerdings gibt es nicht fuer alle Operatoren einen aequivalenten Closurebefehl (z.B. '->' hat kein ' #'-> ').
Erste Schritte zur Programmierung
Man kann Closures ganz einfach erzeugen, wenn man weiss wie man sie aus LPC-Quelltext erzeugen kann. Man muss also eine fertige Closure nicht verstehen oder gar zurueckuebersetzen koennen, oder aber eine Closure einfach so aus dem Gedaechtnis bilden. Ich werde einfach nur versuchen zu zeigen wie man ein Stueck LPC-Quelltext in eine Closure umwandeln kann.
Beispiele
Operator | Closure | Beispiel
-----------+----------------+------------------------------------------
+=, -= | #'+=, #'-= | x+=1; ({ #'+=, x, 1 })
*=, /=, %= | #'*=, #'/=, |
| #'%= | x*=y; ({ #'*=, x, y })
<, >, != | #'<, #'>, #'!= | x!=-1; ({ #'!=, x, -1 })
-> | #'call_other | xyz->set_hp(5)
| | ({ #'call_other,xyz,"add_hp",-5 })
| | ({ #'call_other,({#'this_player}),"add_hp",-5 })
befehl1; | #',, | ({ #',, befehl1, befehl2 })
befehl2; | | Es koennen mehrere Befehle hintereinander
| | ausgefuehrt werden.
Weiteres dazu steht in der Englischen Doku: /doc/lpc/closures
Funktionen die in der Enzy als EFun's ausgewiesen sind koennen einfach bei ihrem Namen eingehaengt werden (z.B. ({ #'living, ({ #'this_player }) }) ) LFun's muessen immer ueber #'call_other aufgerufen werden.
Was ist mit Returnwerten?
Ein return ist in einer Closure nicht noetig, das Ergebnis der zuletzt aufgerufenen Funktion ist immer auch der Returnwert der Closure.
Beispiel: lambda( ({}) , ({ #'this_player }) ); - liefert this_player(); lambda( ({}) , ({ #',, tue_was, tue_nochwas, ({ #'this_player }) }) ); - liefert auch this_player();
Noch ein paar sinnvolle (!) Programmbeispiele
(Anmerkung von Avatar: Das Fallenbeispiel habe ich geloescht, da es a) nicht funktionierte, und b) ein sehr schlechtes Beispiel fuer lokale Variablen war)
Hier einige nuetzliche Beispiele fuer den taeglichen Bedarf:
object *spieler;
spieler=filter_array(all_inventory(raum_object),
lambda(({'item}),({#'interactive,'item})));
Was wird hier gemacht:
* filter_array() ruft bei jedem Element des Arrays aus dem 1. Argument
die Funktion im 2. Argument auf
* Die Funktion all_inventory(raum_object) liefert alle Gegenstaende
des Raumes 'raum_object'. 'raum_object' koennte z.B. this_object()
sein.
* Die Lambda-Funktion liefert eine '1' zurueck, wenn es sich bei
dem Element 'obj (diese Variable wird von filter_array() uebergeben,
der Name kann uebrigens willkuerlich gewaehlt werden) um ein
"Interaktives Objekt" (also einem Spieler) handelt.
* Zurueckgegeben werden alle Interaktiven Objekte - also alle
Spieler des Raumes
object *goetter;
goetter=filter_array(users(),
lambda(({'player}),
({#'==, ({#'call_other, 'player, "query_rasse"}), "gott"})));
Was wird hier gemacht:
* filter_array - siehe voriges Beispiel
* Die Funktion users() liefert alle eingeloggten Spieler
* Die Lambda-Closure ruft "spieler->query_rasse()" auf, und
ueberprueft, ob das Ergebnis "gott" ist
* Zurueckgegeben werden alle eingeloggten Goetter
string *namen;
namen=map_array(users(),
lambda(({#'player}),
({#'call_other, 'player, "query_cap_name"})));
Was wird gemacht:
* map_array() ersetzt jedes Element aus dem Array des 1. Argumentes
durch das Resultat, welches die Funktion im 2. Argument
zurueckliefert.
* users() liefert alle eingeloggten Spieler
* Die Lambda-Closure ruft "spieler->query_cap_name()" auf, und
ersetzt das Element 'player durch das Resultat dieses Call-Others
(also dem Cap-Namen)
* Zurueckgegeben werden die Namen aller eingeloggten Spieler.
UEBRIGENS: Dieses Problem kann man auch einfacher loesen:
namen=map_objects(users(),"query_cap_name");
...aber hier soll ja gezeigt werden, wie man mit Closures umgeht. ;-)
Schlusswort
Wir hoffen, Ihr habt das jetzt einigermassen verstanden. Wer an dieser Stelle noch weiter machen will sollte sich die englischen Texte in der Enzy mal antun, und ich denke jetzt duerften auch die meisten Sachen da verstaendlich sein. Wer denkt er braucht nie wieder Lambda()-Closures hat auch recht, weil man ja auch alles mit simplen Closure-Funktionsaufrufen umgehen kann, aber es ist eben schoener mit. Zudem koennte eine einmal entwickelte Closure vielen Programmierern die Arbeit erleichtern (siehe /sys/item.h). In diesem Sinne, Closures sollten den Leuten bleiben die anderen das Leben leichter und sich ihres schwerer machen wollen.

