MG Mud User | 88f1247 | 2016-06-24 23:31:02 +0200 | [diff] [blame^] | 1 | CONCEPT |
| 2 | closures |
| 3 | |
| 4 | NOTE |
| 5 | This is the official man page concerning closures. If you find |
| 6 | it hard to read maybe the Closure Guide is an easier introduction |
| 7 | for you: See closure_guide(LPC) and closures-example(LPC). |
| 8 | |
| 9 | DESCRIPTION |
| 10 | Closures provide a means of creating code dynamically and |
| 11 | passing pieces of code as parameters, storing them in |
| 12 | variables. One might think of them as a very advanced form of |
| 13 | process_string(). However, this falls short of what you can |
| 14 | actually do with them. |
| 15 | |
| 16 | The simplest kind of closures are efuns, lfuns or operators. |
| 17 | For example, #'this_player is an example of a closure. You can |
| 18 | assign it to a variable as in |
| 19 | |
| 20 | closure f; |
| 21 | object p; |
| 22 | f = #'this_player; |
| 23 | |
| 24 | and later use either the funcall() or apply() efun to evaluate |
| 25 | it. Like |
| 26 | |
| 27 | p = funcall(f); |
| 28 | |
| 29 | or |
| 30 | |
| 31 | p = apply(f); |
| 32 | |
| 33 | In both cases there p will afterwards hold the value of |
| 34 | this_player(). Of course, this is only a rather simple |
| 35 | application. |
| 36 | |
| 37 | Inline closures are a variant of lfun closures, the difference |
| 38 | being that the function text is written right where the |
| 39 | closure is used. Since they are pretty powerful by |
| 40 | themselves, inline closures have their own manpage. |
| 41 | |
| 42 | More useful instances of closures can be created |
| 43 | using the lambda() efun. It is much like the lambda function |
| 44 | in LISP. For example, you can do the following: |
| 45 | |
| 46 | f = lambda( ({ 'x }), ({ #'environment, 'x }) ); |
| 47 | |
| 48 | This will create a lambda closure and assign it to f. The |
| 49 | first argument to lambda is an array describing the arguments |
| 50 | (symbols) passed to the closure upon evaluation by funcall() |
| 51 | or apply(). You can now evaluate f, for example by means of |
| 52 | funcall(f,this_object()). This will result in the following |
| 53 | steps: |
| 54 | |
| 55 | 1. The value of this_object() will be bound to symbol x. |
| 56 | 2. environment(x) evaluates to environment(this_object()) |
| 57 | and is returned as the result of the funcall(). |
| 58 | |
| 59 | One might wonder why there are two functions, funcall() and |
| 60 | apply(), to perform the seemingly same job, namely evaluating |
| 61 | a closure. Of course there is a subtle difference. If the last |
| 62 | argument to apply() is an array, then each of its elements |
| 63 | gets expanded to an additional paramater. The obvious use |
| 64 | would be #'call_other as in: |
| 65 | |
| 66 | mixed eval(object ob,string func,mixed *args) { |
| 67 | return apply(#'call_other,ob,func,args); |
| 68 | } |
| 69 | |
| 70 | This will result in calling |
| 71 | ob->func(args[0],args[1],...,args[sizeof(args)-1]). Using |
| 72 | funcall() instead of apply() would have given us |
| 73 | ob->func(args). |
| 74 | |
| 75 | Of course, besides efuns there are closures for operators, |
| 76 | like #'+, '-, #'<, #'&&, etc. |
| 77 | |
| 78 | Well, so far closures have been pretty much limited despite |
| 79 | their obvious flexibility. This changes now with the |
| 80 | introduction of conditional and loop operators. For example, |
| 81 | try: |
| 82 | |
| 83 | closure max; |
| 84 | max = lambda( ({ 'x, 'y }), |
| 85 | ({ #'? ,({ #'>, 'x, 'y }), 'x, 'y }) ); |
| 86 | return funcall(max,7,3); |
| 87 | |
| 88 | The above example will return 7. What happened? Of course #'? |
| 89 | is the conditional operator and its 'syntax' is as follows: |
| 90 | |
| 91 | ({ #'?, cond1, val1, cond2, val2, ..., condn, valn, |
| 92 | valdefault }); |
| 93 | |
| 94 | It evaluates cond1, cond2, ..., condn successively until it |
| 95 | gets a nonzero result and then returns the corresponding |
| 96 | value. If there is no condition evaluating to a nonzero |
| 97 | result, valdefault gets returned. If valdefault is omitted, 0 |
| 98 | gets returned. #'?! works just like #'?, except that the ! |
| 99 | operator is applied to conditions before testing. Therefore, |
| 100 | while #'? is somewhat like an if statement, #'?! resembles an |
| 101 | if_not statement if there were one. |
| 102 | |
| 103 | There are also loops: |
| 104 | |
| 105 | ({ #'do, loopbody1, ..., loopbodyN, loopcond, loopresult }) |
| 106 | |
| 107 | will evaluate the loopbodies until loopcond evaluates to 0 and |
| 108 | then return the value of loopresult. Symbols may be used as |
| 109 | variables, of course. |
| 110 | |
| 111 | ({ #'while, loopcond, loopresult, loopbody1, ..., loopbodyN }) |
| 112 | |
| 113 | works similar but evaluates loopcond before the loopbodies. |
| 114 | |
| 115 | The foreach() loop also exists: |
| 116 | |
| 117 | ({ #'foreach, 'var, expr, loopbody1, ..., loopbodyN }) |
| 118 | ({ #'foreach, ({ 'var1, ..., 'varN}) , expr |
| 119 | , loopbody1, ..., loopbodyN }) |
| 120 | |
| 121 | |
| 122 | Now on to a couple of tricky things: |
| 123 | |
| 124 | a) How do I write down an array within a lambda closure to |
| 125 | avoid interpretation as a subclosure? |
| 126 | |
| 127 | ({ #'member, ({ "abc", "xyz" }), 'x }) will obviously |
| 128 | result in an error as soon as lambda() tries to interpret |
| 129 | "abc" as a closure operator. The solution is to quote the |
| 130 | array, as in: ({ #'member, '({ "abc", "xyz" }), 'x }). |
| 131 | Applying lambda() to this will not result in an error. |
| 132 | Instead, the quote will be stripped from the array and the |
| 133 | result regarded as a normal array literal. The same can be |
| 134 | achieved by using the efun quote(), e.g.: |
| 135 | |
| 136 | ({ #'member, quote( ({ "abc", "xyz" }), 'x ) }) |
| 137 | |
| 138 | b) Isn't it a security risk to pass, say, a closure to the |
| 139 | master object which then evaluates it with all the |
| 140 | permissions it got? |
| 141 | |
| 142 | Luckily, no. Each closure gets upon compilation bound to |
| 143 | the object defining it. That means that executing it first |
| 144 | sets this_object() to the object that defined it and then |
| 145 | evaluates the closure. This also allows us to call lfuns |
| 146 | which might otherwise be undefined in the calling object. |
| 147 | |
| 148 | There is however, a variant of lambda(), called |
| 149 | unbound_lambda(), which works similar but does not allow |
| 150 | the use of lfuns and does not bind the closure to the |
| 151 | defining object. The drawback is that trying to evaluate it |
| 152 | by apply() or funcall() will result in an error. The |
| 153 | closure first needs to be bound by calling bind_lambda(). |
| 154 | bind_lambda() normally takes one argument and transforms an |
| 155 | unbound closure into a closure bound to the object |
| 156 | executing the bind_lambda(). |
| 157 | |
| 158 | Privileged objects, like the master and the simul_efun |
| 159 | object (or those authorized by the privilege_violation() |
| 160 | function in the master) may also give an object as the |
| 161 | second argument to bind_lambda(). This will bind the |
| 162 | closure to that object. A sample application is: |
| 163 | |
| 164 | dump_object(ob) |
| 165 | // will dump the variables of ob to /dump.o |
| 166 | { |
| 167 | closure save; |
| 168 | save = unbound_lambda( ({ }), |
| 169 | ({ #'save_object, "/open/dump" }) ); |
| 170 | bind_lambda(save,ob); |
| 171 | funcall(save); |
| 172 | } |
| 173 | |
| 174 | bind_lambda() can also be used with efun closures. |
| 175 | |
| 176 | c) It might be an interesting application to create closures |
| 177 | dynamically as an alternative to writing LPC code to a file |
| 178 | and then loading it. However, how do I avoid doing exactly |
| 179 | that if I need symbols like 'x or 'y? |
| 180 | |
| 181 | To do that one uses the quote() efun. It takes a string as |
| 182 | its argument and transforms it into a symbol. For example, |
| 183 | writing quote("x") is exactly the same as writing 'x. |
| 184 | |
| 185 | d) How do I test if a variable holds a closure? |
| 186 | |
| 187 | Use the closurep() efun which works like all the other type |
| 188 | testing efuns. For symbols there is also symbolp() |
| 189 | available. |
| 190 | |
| 191 | e) That means, I can do: |
| 192 | if (closurep(f)) return funcall(f); else return f; ? |
| 193 | |
| 194 | Yes, but in the case of funcall() it is unnecessary. If |
| 195 | funcall() gets only one argument and it is not a closure it |
| 196 | will be returned unchanged. So return funcall(f); would |
| 197 | suffice. |
| 198 | |
| 199 | f) I want to use a function in some object as a closure. How do I do |
| 200 | that? |
| 201 | |
| 202 | There are several ways. If the function resides in |
| 203 | this_object(), just use #'func_name. If not, or if you want |
| 204 | to create the function dnynamically, use the efun |
| 205 | symbol_function(). It takes a string as it first and an |
| 206 | object as its second argument and returns a closure which |
| 207 | upon evaluation calls the given function in the given |
| 208 | object (and faster than call_other(), too, if done from |
| 209 | inside a loop, since function search will be done only when |
| 210 | calling symbol_function(). |
| 211 | |
| 212 | g) Can I create efun closures dynamically, too? |
| 213 | |
| 214 | Yes, just use symbol_function() with a single argument. |
| 215 | Most useful for marker objects and the like. But |
| 216 | theoretically a security risk if not used properly and from |
| 217 | inside a security relevant object. Take care, however, |
| 218 | that, if there is a simul_efun with the same name, it will |
| 219 | be preferred as in the case of #'function. Use the efun:: |
| 220 | modifier to get the efun if you need it. |
| 221 | |
| 222 | h) Are there other uses of closures except using them to store |
| 223 | code? |
| 224 | |
| 225 | Lots. For example, you can use them within almost all of |
| 226 | the efuns where you give a function as an argument, like |
| 227 | filter(), sort_array() or walk_mapping(). |
| 228 | sort_array(array,#'>) does indeed what is expected. Another |
| 229 | application is set_prompt(), where a closure can output |
| 230 | your own prompt based on the current time and other stuff |
| 231 | which changes all the time. |
| 232 | |
| 233 | Finally, there are some special efun/operator closures: |
| 234 | |
| 235 | #'[ : indexes an array. |
| 236 | #'[< : does the same, but starting at the end. |
| 237 | #'[..] : gets an array and two numbers |
| 238 | and returns a sub-array. |
| 239 | #'[..<] : same as above but the second index is |
| 240 | interpreted as counted from the left end. |
| 241 | #'[<..] and |
| 242 | #'[<..<] : should be clear now. |
| 243 | #'[.. : takes only one index and returns the sub- |
| 244 | array from this index to the end. |
| 245 | #'[<.. : same as above but the index is interpreted |
| 246 | as counted from the left end. |
| 247 | #'({ : puts all arguments into an array. |
| 248 | #'([ : gets an unquoted (!) array which must include |
| 249 | at least one element as argument and returns a mapping of |
| 250 | the width of the given array's size with one entry that |
| 251 | contains the first element as key and the other elements |
| 252 | as values to the key. |
| 253 | |
| 254 | #'negate is for unary minus. |
| 255 | #', may be followed by any number of closures, |
| 256 | e.g.: ({ (#',), |
| 257 | ({#'= 'h, 'a, }), ({#'=, 'a, 'b }), ({#'=, 'b, 'h }) }) |
| 258 | will swap 'a and 'b when compiled and executed. |
| 259 | |
| 260 | |
| 261 | Procedural elements: |
| 262 | ==================== |
| 263 | |
| 264 | definition of terms: |
| 265 | <block> : zero or more values to be evaluated. |
| 266 | <test> : one value to be evaluated as branch or loop condition. |
| 267 | <result> : one value to be evaluated at the end of the |
| 268 | execution of the form; the value is returned. |
| 269 | <lvalue> : local variable/parameter, global variable, or an |
| 270 | indexed lvalue. |
| 271 | useded EBNF operators: |
| 272 | { } iteration |
| 273 | [ ] option |
| 274 | |
| 275 | forms: |
| 276 | ({#', <body> <result>}) |
| 277 | ({#'? { <test> <result> } [ <result> ] }) |
| 278 | ({#'?! { <test> <result> } [ <result> ] }) |
| 279 | ({#'&& { test } }) |
| 280 | ({#'|| { test } }) |
| 281 | ({#'while <test> <result> <body>}) loop while test |
| 282 | evaluates non-zero. |
| 283 | ({#'do <body> <test> <result>}) loop till test |
| 284 | evaluates zero. |
| 285 | ({#'= { <lvalue> <value> } }) assignment |
| 286 | other assignment |
| 287 | operators work, too. |
| 288 | |
| 289 | lisp similars: |
| 290 | #', progn |
| 291 | #'? cond |
| 292 | #'&& and |
| 293 | #'|| or |
| 294 | #'while do /* but lisp has more syntactic candy here */ |
| 295 | #'= setq |
| 296 | |
| 297 | A parameter / local variable 'foo' is referenced as 'foo , a |
| 298 | global variable as ({#'foo}) . In lvalue positions |
| 299 | (assignment), you need not enclose global variable closures in |
| 300 | arrays. |
| 301 | |
| 302 | Call by reference parameters are given with ({#'&, <lvalue>}) |
| 303 | |
| 304 | Some special efuns: |
| 305 | #'[ indexing |
| 306 | #'[< indexing from the end |
| 307 | #'negate unary - |
| 308 | |
| 309 | Unbound lambda closures |
| 310 | ======================= |
| 311 | |
| 312 | These closures are not bound to any object. They are created |
| 313 | with the efun unbound_lambda() . They cannot contain |
| 314 | references to global variables, and all lfun closures are |
| 315 | inserted as is, since there is no native object for this |
| 316 | closure. You can bind and rebind unbound lambda closures to |
| 317 | an object with efun bind_lambda() You need to bind it before |
| 318 | it can be called. Ordinary objects can obly bind to |
| 319 | themselves, binding to other objects causes a privilege |
| 320 | violation(). The point is that previous_object for calls done |
| 321 | from inside the closure will reflect the object doing |
| 322 | bind_lambda(), and all object / uid based security will also |
| 323 | refer to this object. |
| 324 | |
| 325 | |
| 326 | AUTHOR |
| 327 | MacBeth, Amylaar, Hyp |
| 328 | |
| 329 | SEE ALSO |
| 330 | closures-abstract(LPC), closures-example(LPC), closure_guide(LPC) |
| 331 | inline-closures(LPC) |