blob: fc528940e98e2f3752a65c0bbb3c4cc451d62bc1 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001Closures provide a means of creating code dynamically and passing pieces
2of code as parameters, storing them in variables. One might think of them
3as a very advanced form of process_string(). However, this falls short of
4what you can actually do with them.
5
6The simplest kind of closures are efuns, lfuns or operators. For
7example, #'this_player is an example of a closure. You can assign it
8to a variable as in
9
10 closure f;
11 object p;
12 f = #'this_player;
13
14and later use either the funcall() or apply() efun to evaluate it. Like
15
16 p = funcall(f);
17
18or
19 p = apply(f);
20
21In both cases there p will afterwards hold the value of this_player().
22Of course, this is only a rather simple application. More useful
23instances of closures can be created using the lambda() efun. It is much
24like the lambda function in LISP. For example, you can do the following:
25
26 f = lambda( ({ 'x }), ({ #'environment, 'x }) );
27
28This will create a lambda closure and assign it to f. The first argument
29to lambda is an array describing the arguments (symbols) passed to the
30closure upon evaluation by funcall() or apply(). You can now evaluate f,
31for example by means of funcall(f,this_object()). This will result in
32the following steps:
33
34 1. The value of this_object() will be bound to symbol x.
35 2. environment(x) evaluates to environment(this_object())
36 and is returned as the result of the funcall().
37
38One might wonder why there are two functions, funcall() and apply(), to
39perform the seemingly same job, namely evaluating a closure. Of course
40there is a subtle difference. If the last argument to apply() is an array,
41then each of its elements gets expanded to an additional paramater. The
42obvious use would be #'call_other as in:
43
44 mixed eval(object ob,string func,mixed *args)
45 {
46 return apply(#'call_other,ob,func,args);
47 }
48
49This will result in calling ob->func(args[0],args[1],...,args[sizeof(args)-1]).
50Using funcall() instead of apply() would have given us ob->func(args).
51
52Of course, besides efuns there are closures for operators, like #'+,
53#'-, #'<, #'&&, etc.
54
55Well, so far closures have been pretty much limited despite their
56obvious flexibility. This changes now with the introduction of
57conditional and loop operators. For example, try:
58
59 closure max;
60 max = lambda( ({ 'x, 'y }), ({ #'? ,({ #'>, 'x, 'y}), 'x, 'y }) });
61 return funcall(max,7,3);
62
63The above example will return 7. What happened? Of course #'? is the
64conditional operator and its 'syntax' is as follows:
65
66 ({ #'?, cond1, val1, cond2, val2, ..., condn, valn, valdefault });
67
68It evaluates cond1, cond2, ..., condn successively until it gets a
69nonzero result and then returns the corresponding value. If there is no
70condition evaluating to a nonzero result, valdefault gets returned. If
71valdefault is omitted, 0 gets returned. #'?! works just like #'?, except
72that the ! operator is applied to conditions before testing. Therefore,
73while #'? is somewhat like an if statement, #'?! resembles an if_not
74statement if there were one.
75
76 There are also loops:
77 ({ #'do, loopbody, loopcond, loopresult })
78 will evaluate loopbody until loopcond evaluates to 0 and
79 then return the value of loopresult. Symbols my be used
80 as variables, of course.
81
82 ({ #'while, loopcond, loopresult, loopbody })
83 works similar but evaluates loopcond before loopbody.
84
85There are, however, some questions open:
86
87a) How do I write down an array within a lambda closure to avoid
88 interpretation as a subclosure?
89 ({ #'member_array, 'x, ({ "abc", "xyz }) }) will obviously result in
90 an error as soon as lambda() tries to interpret "abc" as a closure
91 operator. The solution is to quote the array, as in:
92 ({ #'member_array, 'x, '({ "abc", "xyz }) }). Applying lambda() to
93 this will not result in an error. Instead, the quote will be stripped
94 from the array and the result regarded as a normal array literal. The
95 same can be achieved by using the efun quote(), e.g.:
96 ({ #'member_array, 'x, quote( ({ "abc", "xyz }) ) })
97b) Isn't it a security risk to pass, say, a closure to the master object
98 which then evaluates it with all the permissions it got?
99 Luckily, no. Each closure gets upon compilation bound to the object
100 defining it. That means that executing it first sets this_object()
101 to the object that defined it and then evaluates the closure. This
102 also allows us to call lfuns which might otherwise be undefined in
103 the calling object.
104 There is however, a variant of lambda(), called unbound_lambda(),
105 which works similar but does not allow the use of lfuns and does not
106 bind the closure to the defining object. The drawback is that trying
107 to evaluate it by apply() or funcall() will result in an error. The
108 closure first needs to be bound by calling bind_lambda().
109 bind_lambda() normally takes one argument and transforms an unbound
110 closure into a closure bound to the object executing the
111 bind_lambda().
112 Privileged objects, like the master and the simul_efun object (or
113 those authorized by the privilege_violation() function in the master)
114 may also give an object as the second argument to bind_lambda(). This
115 will bind the closure to that object. A sample application is:
116
117 dump_object(ob)
118 // will dump the variables of ob to /dump.o
119 {
120 closure save;
121 save = unbound_lambda( ({ }), ({ #'save_object, "/open/dump" }) );
122 bind_lambda(save,ob);
123 funcall(save);
124 }
125
126 bind_lambda() can also be used with efun closures.
127
128c) It might be an interesting application to create closures dynamically
129 as an alternative to writing LPC code to a file and then loading it.
130 However, how do I avoid doing exactly that if I need symbols like 'x
131 or 'y?
132 To do that one uses the quote() efun. It takes a string as its
133 argument and transforms it into a symbol. For example, writing
134 quote("x") is exactly the same as writing 'x.
135
136d) How do I test if a variable holds a closure?
137 Use the closurep() efun which works like all the other type testing
138 efuns. For symbols there is also symbolp() available.
139
140e) That means, I can do:
141 if (closurep(f)) return funcall(f); else return f; ?
142 Yes, but in the case of funcall() it is unnecessary. If funcall()
143 gets only one argument and it is not a closure it will be returned
144 unchanged. So return funcall(f); would suffice.
145
146f) I want to use a function in some object as a closure. How do I do
147 that?
148 There are several ways. If the function resides in this_object(),
149 just use #'func_name. If not, or if you want to create the function
150 dnynamically, use the efun symbol_function(). It takes a string as
151 it first and an object as its second argument and returns a closure
152 which upon evaluation calls the given function in the given object
153 (and faster than call_other(), too, if done from inside a loop,
154 since function search will be done only when calling symbol_function().
155
156g) Can I create efun closures dynamically, too?
157 Yes, just use symbol_function() with a single argument. Most useful
158 for marker objects and the like. But theoretically a security risk
159 if not used properly and from inside a security relevant object.
160 Take care, however, that, if there is a simul_efun with the same
161 name, it will be preferred as in the case of #'function. Use the
162 efun:: modifier to get the efun if you need it.
163
164h) Are there other uses of closures except using them to store code?
165 Lots. For example, you can use them within almost all of the
166 efuns where you give a function as an argument, like filter(),
167 sort_array() or walk_mapping(). sort_array(array,#'>) does indeed
168 what is expected. Another application is set_prompt(), where a
169 closure can output your own prompt based on the current time and other
170 stuff which changes all the time.
171
172Finally, there are some special efun/operator closures:
173
174#'[ indexes an array.
175#'[< does the same, but starting at the end.
176#'negate is for unary minus.
177#', may be followed by any number of closures,
178e.g.: ({ #', ({#'= 'h, 'a, }), ({#'=, 'a, 'b }), ({#'=, 'b, 'h }) })
179will swap 'a and 'b when compiled and executed.
180
181------------
182An example from Amylaar:
183
184#I have tested the replace_program() functionality with one example, which I
185#include below. The room is commonly known as /room/orc_valley.c .
186#A prerequisite to make this work is to have valued properties in room.c .
187#The property C_EXTRA_RESET, if defined, is evaluated at reset time in
188#the reset() of the used room.c .
189#Moreover, you need a function to query an unprocessed property to use
190#orc_valley.c from fortress.c (That is, don't do an automatic funcall there.)
191#If you can't supply such a function, you have to set the property "get_orc"
192#at the end of orc_valley.c 's extra_reset() with:
193# add_prop("get_orc", lambda(0, get_orc) );
194#which will set the property to a function that returns the function that
195#is in the variable get_orc at the time you do the add_prop() call.
196#
197#Back to fortress.c : Assume you have the function successfully queried and
198#stored in the variable get_orc. Now you can compute your extra_reset()
199#function by:
200# get_orc = lambda( 0, ({#'funcall, get_orc, 8, 40}) );
201#which creates the usual 8 orcs with an a_chat chance of 40.
202#
203#Here comes the orc_valley.c source:
204#
205# ----------- cut here ------- cut here -------- cut here ------------
206#
207##include "room.h"
208##include "/sys/stdproperties.h"
209##undef EXTRA_RESET
210##define EXTRA_RESET extra_reset();
211#
212#extra_reset() {
213# closure get_orc;
214#
215# replace_program("room/room"); /* Must come first. */
216# get_orc = lambda( ({'num_orcs, 'chat_chance}),
217# ({#'?!, ({#'present, "orc", ({#'previous_object}) }),
218# ({#'do,
219# ({#'=, 'orc, ({#'clone_object, "obj/monster"}) }),
220# ({#'=, 'i, 9}),
221# ({#'do,
222# ({#'call_other, 'orc, "set_level",
223# ({#'+, ({#'random, 2}), 1}) }),
224# ({#'call_other, 'orc,
225# ({#'[,
226# quote(({"set_aggressive", "set_ac", "set_short",
227# "set_al", "set_ep", "set_hp", "set_race",
228# "set_alias", "set_name"})), ({#'-=, 'i, 1}) }),
229# ({#'[,
230# quote(({1, 0, "An orc", -60, 1014, 30, "orc",
231# "dirty crap", "orc"})), 'i}) }),
232# 'i, 0}),
233# ({#'call_other, 'orc, "load_a_chat", 'chat_chance,
234# quote(({ "Orc says: Kill 'em!\n",
235# "Orc says: Bloody humans!\n",
236# "Orc says: Stop 'em!\n",
237# "Orc says: Get 'em!\n",
238# "Orc says: Let's rip out his guts!\n",
239# "Orc says: Kill 'em before they run away!\n",
240# "Orc says: What is that human doing here!\n",
241# })) }),
242# ({#'=, 'n, ({#'*, ({#'random, 3}), 5}) }),
243# ({#'=, 'weapon, ({#'clone_object, "obj/weapon"}) }),
244# ({#'=, 'i, 5}),
245# ({#'do,
246# ({#'call_other, 'weapon,
247# ({#'[,
248# quote(({ "set_alt_name", "set_weight", "set_value",
249# "set_class", "set_name"})), ({#'-=, 'i, 1}) }),
250# ({#'[,
251# quote(({ "knife", 1, 8, 5, "knife",
252# "knife", 1, 15, 7, "curved knife",
253# "axe", 2, 25, 9, "hand axe", })),
254# ({#'+, 'n, 'i}) }) }),
255# 'i, 0}),
256# ({#'transfer, 'weapon, 'orc}),
257# ({#'command,
258# ({#'+, "wield ",
259# ({#'call_other, 'weapon, "query_name"}) }), 'orc}),
260# ({#'move_object, 'orc, ({#'previous_object}) }),
261# ({#'-=, 'num_orcs, 1}), 0})
262# })
263# );
264# add_prop("get_orc", get_orc);
265# get_orc = lambda( 0, ({#'funcall, get_orc, 2, 50}) );
266# add_prop(C_EXTRA_RESET, get_orc);
267# funcall(get_orc);
268#}
269#
270#TWO_EXIT("room/slope", "east",
271# "room/fortress", "north",
272# "The orc valley",
273# "You are in the orc valley. This place is inhabited by orcs.\n" +
274# "There is a fortress to the north, with lot of signs of orcs.\n", 1)
275#
276# ----------- cut here ------- cut here -------- cut here ------------
277#
278Procedural elements:
279====================
280
281definition of terms:
282 <block> : zero or more values to be evaluated.
283 <test> : one value to be evaluated as branch or loop condition.
284 <result> : one value to be evaluated at the end of the execution of the
285 form; the value is returned.
286 <lvalue> : local variable/parameter, global variable, or an indexed lvalue.
287useded EBNF operators:
288{ } iteration
289[ ] option
290
291forms:
292 ({#', <body> <result>})
293 ({#'? { <test> <result> } [ <result> ] })
294 ({#'?! { <test> <result> } [ <result> ] })
295 ({#'&& { test } })
296 ({#'|| { test } })
297 ({#'while <test> <result> <body>}) loop while test evaluates non-zero.
298 ({#'do <body> <test> <result>}) loop till test evaluates zero.
299 ({#'= { <lvalue> <value> } }) assignment
300 other assignment operators work too.
301
302lisp similars:
303 #', progn
304 #'? cond
305 #'&& and
306 #'|| or
307 #'while do /* but lisp has more syntactic candy here */
308 #'= setq
309
310A parameter / local variable 'foo' is referenced as 'foo , a global
311variable as ({#'foo}) . In lvalue positions (assignment), you need not
312enclose global variable closures in arrays.
313
314Call by reference parameters are given with ({#'&, <lvalue>})
315
316Some special efuns:
317#'[ indexing
318#'[< indexing from the end
319#'negate unary -
320
321Unbound lambda closures
322=======================
323
324These closures are not bound to any object. They are created with the efun
325unbound_lambda() . They cannot contain references to global variables, and
326all lfun closures are inserted as is, since there is no native object for
327this closure.
328You can bind and rebind unbound lambda closures to an object with efun
329bind_lambda() You need to bind it before it can be called. Ordinary objects
330can obly bind to themselves, binding to other objects causes a privilege
331violation().
332The point is that previous_object for calls done from inside the closure
333will reflect the object doing bind_lambda(), and all object / uid based
334security will also refer to this object.
335
336
337The following is mostly vapourware.
338Well, another application would be that some things in the driver can be,
339sort of, microprogrammed.
340The master object could set some hooks in inaugurate_master(), like creating
341the code for move_object(), using a primitive low_move_object() or
342__move_object() or such. All calls of init(), exit(), etc. can thus be
343controlled on mudlib level.
344The driver would do an implicit bind_lambda() to the victim when the closure
345is used.
346
347e.g.
348({#'?, ({#'=, 'ob, ({#'first_inventory, 'destination}) }),
349 ({#'do,
350 ({#'call_other, 'ob, "init"}),
351 ({#'=, 'ob, ({#'next_inventory, 'ob}) }), 0 })
352})
353
354or
355
356({#'filter_objects, ({#'all_inventory, 'destination}), "init"})
357/* Won't show init failures due to move/destruct */
358
359is equivalent to
360
361if (ob = first_inventory(destination) ) {
362 do {
363 ob->init();
364 } while(ob = next_inventory(ob) );
365}
366
367and it's speed is mainly determined by the call_other. Thus, it shouldn't be
368noticably slower than the current C code in move_object().
369