| Closures provide a means of creating code dynamically and passing pieces |
| of code as parameters, storing them in variables. One might think of them |
| as a very advanced form of process_string(). However, this falls short of |
| what you can actually do with them. |
| |
| The simplest kind of closures are efuns, lfuns or operators. For |
| example, #'this_player is an example of a closure. You can assign it |
| to a variable as in |
| |
| closure f; |
| object p; |
| f = #'this_player; |
| |
| and later use either the funcall() or apply() efun to evaluate it. Like |
| |
| p = funcall(f); |
| |
| or |
| p = apply(f); |
| |
| In both cases there p will afterwards hold the value of this_player(). |
| Of course, this is only a rather simple application. More useful |
| instances of closures can be created using the lambda() efun. It is much |
| like the lambda function in LISP. For example, you can do the following: |
| |
| f = lambda( ({ 'x }), ({ #'environment, 'x }) ); |
| |
| This will create a lambda closure and assign it to f. The first argument |
| to lambda is an array describing the arguments (symbols) passed to the |
| closure upon evaluation by funcall() or apply(). You can now evaluate f, |
| for example by means of funcall(f,this_object()). This will result in |
| the following steps: |
| |
| 1. The value of this_object() will be bound to symbol x. |
| 2. environment(x) evaluates to environment(this_object()) |
| and is returned as the result of the funcall(). |
| |
| One might wonder why there are two functions, funcall() and apply(), to |
| perform the seemingly same job, namely evaluating a closure. Of course |
| there is a subtle difference. If the last argument to apply() is an array, |
| then each of its elements gets expanded to an additional paramater. The |
| obvious use would be #'call_other as in: |
| |
| mixed eval(object ob,string func,mixed *args) |
| { |
| return apply(#'call_other,ob,func,args); |
| } |
| |
| This will result in calling ob->func(args[0],args[1],...,args[sizeof(args)-1]). |
| Using funcall() instead of apply() would have given us ob->func(args). |
| |
| Of course, besides efuns there are closures for operators, like #'+, |
| #'-, #'<, #'&&, etc. |
| |
| Well, so far closures have been pretty much limited despite their |
| obvious flexibility. This changes now with the introduction of |
| conditional and loop operators. For example, try: |
| |
| closure max; |
| max = lambda( ({ 'x, 'y }), ({ #'? ,({ #'>, 'x, 'y}), 'x, 'y }) }); |
| return funcall(max,7,3); |
| |
| The above example will return 7. What happened? Of course #'? is the |
| conditional operator and its 'syntax' is as follows: |
| |
| ({ #'?, cond1, val1, cond2, val2, ..., condn, valn, valdefault }); |
| |
| It evaluates cond1, cond2, ..., condn successively until it gets a |
| nonzero result and then returns the corresponding value. If there is no |
| condition evaluating to a nonzero result, valdefault gets returned. If |
| valdefault is omitted, 0 gets returned. #'?! works just like #'?, except |
| that the ! operator is applied to conditions before testing. Therefore, |
| while #'? is somewhat like an if statement, #'?! resembles an if_not |
| statement if there were one. |
| |
| There are also loops: |
| ({ #'do, loopbody, loopcond, loopresult }) |
| will evaluate loopbody until loopcond evaluates to 0 and |
| then return the value of loopresult. Symbols my be used |
| as variables, of course. |
| |
| ({ #'while, loopcond, loopresult, loopbody }) |
| works similar but evaluates loopcond before loopbody. |
| |
| There are, however, some questions open: |
| |
| a) How do I write down an array within a lambda closure to avoid |
| interpretation as a subclosure? |
| ({ #'member_array, 'x, ({ "abc", "xyz }) }) will obviously result in |
| an error as soon as lambda() tries to interpret "abc" as a closure |
| operator. The solution is to quote the array, as in: |
| ({ #'member_array, 'x, '({ "abc", "xyz }) }). Applying lambda() to |
| this will not result in an error. Instead, the quote will be stripped |
| from the array and the result regarded as a normal array literal. The |
| same can be achieved by using the efun quote(), e.g.: |
| ({ #'member_array, 'x, quote( ({ "abc", "xyz }) ) }) |
| b) Isn't it a security risk to pass, say, a closure to the master object |
| which then evaluates it with all the permissions it got? |
| Luckily, no. Each closure gets upon compilation bound to the object |
| defining it. That means that executing it first sets this_object() |
| to the object that defined it and then evaluates the closure. This |
| also allows us to call lfuns which might otherwise be undefined in |
| the calling object. |
| There is however, a variant of lambda(), called unbound_lambda(), |
| which works similar but does not allow the use of lfuns and does not |
| bind the closure to the defining object. The drawback is that trying |
| to evaluate it by apply() or funcall() will result in an error. The |
| closure first needs to be bound by calling bind_lambda(). |
| bind_lambda() normally takes one argument and transforms an unbound |
| closure into a closure bound to the object executing the |
| bind_lambda(). |
| Privileged objects, like the master and the simul_efun object (or |
| those authorized by the privilege_violation() function in the master) |
| may also give an object as the second argument to bind_lambda(). This |
| will bind the closure to that object. A sample application is: |
| |
| dump_object(ob) |
| // will dump the variables of ob to /dump.o |
| { |
| closure save; |
| save = unbound_lambda( ({ }), ({ #'save_object, "/open/dump" }) ); |
| bind_lambda(save,ob); |
| funcall(save); |
| } |
| |
| bind_lambda() can also be used with efun closures. |
| |
| c) It might be an interesting application to create closures dynamically |
| as an alternative to writing LPC code to a file and then loading it. |
| However, how do I avoid doing exactly that if I need symbols like 'x |
| or 'y? |
| To do that one uses the quote() efun. It takes a string as its |
| argument and transforms it into a symbol. For example, writing |
| quote("x") is exactly the same as writing 'x. |
| |
| d) How do I test if a variable holds a closure? |
| Use the closurep() efun which works like all the other type testing |
| efuns. For symbols there is also symbolp() available. |
| |
| e) That means, I can do: |
| if (closurep(f)) return funcall(f); else return f; ? |
| Yes, but in the case of funcall() it is unnecessary. If funcall() |
| gets only one argument and it is not a closure it will be returned |
| unchanged. So return funcall(f); would suffice. |
| |
| f) I want to use a function in some object as a closure. How do I do |
| that? |
| There are several ways. If the function resides in this_object(), |
| just use #'func_name. If not, or if you want to create the function |
| dnynamically, use the efun symbol_function(). It takes a string as |
| it first and an object as its second argument and returns a closure |
| which upon evaluation calls the given function in the given object |
| (and faster than call_other(), too, if done from inside a loop, |
| since function search will be done only when calling symbol_function(). |
| |
| g) Can I create efun closures dynamically, too? |
| Yes, just use symbol_function() with a single argument. Most useful |
| for marker objects and the like. But theoretically a security risk |
| if not used properly and from inside a security relevant object. |
| Take care, however, that, if there is a simul_efun with the same |
| name, it will be preferred as in the case of #'function. Use the |
| efun:: modifier to get the efun if you need it. |
| |
| h) Are there other uses of closures except using them to store code? |
| Lots. For example, you can use them within almost all of the |
| efuns where you give a function as an argument, like filter(), |
| sort_array() or walk_mapping(). sort_array(array,#'>) does indeed |
| what is expected. Another application is set_prompt(), where a |
| closure can output your own prompt based on the current time and other |
| stuff which changes all the time. |
| |
| Finally, there are some special efun/operator closures: |
| |
| #'[ indexes an array. |
| #'[< does the same, but starting at the end. |
| #'negate is for unary minus. |
| #', may be followed by any number of closures, |
| e.g.: ({ #', ({#'= 'h, 'a, }), ({#'=, 'a, 'b }), ({#'=, 'b, 'h }) }) |
| will swap 'a and 'b when compiled and executed. |
| |
| ------------ |
| An example from Amylaar: |
| |
| #I have tested the replace_program() functionality with one example, which I |
| #include below. The room is commonly known as /room/orc_valley.c . |
| #A prerequisite to make this work is to have valued properties in room.c . |
| #The property C_EXTRA_RESET, if defined, is evaluated at reset time in |
| #the reset() of the used room.c . |
| #Moreover, you need a function to query an unprocessed property to use |
| #orc_valley.c from fortress.c (That is, don't do an automatic funcall there.) |
| #If you can't supply such a function, you have to set the property "get_orc" |
| #at the end of orc_valley.c 's extra_reset() with: |
| # add_prop("get_orc", lambda(0, get_orc) ); |
| #which will set the property to a function that returns the function that |
| #is in the variable get_orc at the time you do the add_prop() call. |
| # |
| #Back to fortress.c : Assume you have the function successfully queried and |
| #stored in the variable get_orc. Now you can compute your extra_reset() |
| #function by: |
| # get_orc = lambda( 0, ({#'funcall, get_orc, 8, 40}) ); |
| #which creates the usual 8 orcs with an a_chat chance of 40. |
| # |
| #Here comes the orc_valley.c source: |
| # |
| # ----------- cut here ------- cut here -------- cut here ------------ |
| # |
| ##include "room.h" |
| ##include "/sys/stdproperties.h" |
| ##undef EXTRA_RESET |
| ##define EXTRA_RESET extra_reset(); |
| # |
| #extra_reset() { |
| # closure get_orc; |
| # |
| # replace_program("room/room"); /* Must come first. */ |
| # get_orc = lambda( ({'num_orcs, 'chat_chance}), |
| # ({#'?!, ({#'present, "orc", ({#'previous_object}) }), |
| # ({#'do, |
| # ({#'=, 'orc, ({#'clone_object, "obj/monster"}) }), |
| # ({#'=, 'i, 9}), |
| # ({#'do, |
| # ({#'call_other, 'orc, "set_level", |
| # ({#'+, ({#'random, 2}), 1}) }), |
| # ({#'call_other, 'orc, |
| # ({#'[, |
| # quote(({"set_aggressive", "set_ac", "set_short", |
| # "set_al", "set_ep", "set_hp", "set_race", |
| # "set_alias", "set_name"})), ({#'-=, 'i, 1}) }), |
| # ({#'[, |
| # quote(({1, 0, "An orc", -60, 1014, 30, "orc", |
| # "dirty crap", "orc"})), 'i}) }), |
| # 'i, 0}), |
| # ({#'call_other, 'orc, "load_a_chat", 'chat_chance, |
| # quote(({ "Orc says: Kill 'em!\n", |
| # "Orc says: Bloody humans!\n", |
| # "Orc says: Stop 'em!\n", |
| # "Orc says: Get 'em!\n", |
| # "Orc says: Let's rip out his guts!\n", |
| # "Orc says: Kill 'em before they run away!\n", |
| # "Orc says: What is that human doing here!\n", |
| # })) }), |
| # ({#'=, 'n, ({#'*, ({#'random, 3}), 5}) }), |
| # ({#'=, 'weapon, ({#'clone_object, "obj/weapon"}) }), |
| # ({#'=, 'i, 5}), |
| # ({#'do, |
| # ({#'call_other, 'weapon, |
| # ({#'[, |
| # quote(({ "set_alt_name", "set_weight", "set_value", |
| # "set_class", "set_name"})), ({#'-=, 'i, 1}) }), |
| # ({#'[, |
| # quote(({ "knife", 1, 8, 5, "knife", |
| # "knife", 1, 15, 7, "curved knife", |
| # "axe", 2, 25, 9, "hand axe", })), |
| # ({#'+, 'n, 'i}) }) }), |
| # 'i, 0}), |
| # ({#'transfer, 'weapon, 'orc}), |
| # ({#'command, |
| # ({#'+, "wield ", |
| # ({#'call_other, 'weapon, "query_name"}) }), 'orc}), |
| # ({#'move_object, 'orc, ({#'previous_object}) }), |
| # ({#'-=, 'num_orcs, 1}), 0}) |
| # }) |
| # ); |
| # add_prop("get_orc", get_orc); |
| # get_orc = lambda( 0, ({#'funcall, get_orc, 2, 50}) ); |
| # add_prop(C_EXTRA_RESET, get_orc); |
| # funcall(get_orc); |
| #} |
| # |
| #TWO_EXIT("room/slope", "east", |
| # "room/fortress", "north", |
| # "The orc valley", |
| # "You are in the orc valley. This place is inhabited by orcs.\n" + |
| # "There is a fortress to the north, with lot of signs of orcs.\n", 1) |
| # |
| # ----------- cut here ------- cut here -------- cut here ------------ |
| # |
| Procedural elements: |
| ==================== |
| |
| definition of terms: |
| <block> : zero or more values to be evaluated. |
| <test> : one value to be evaluated as branch or loop condition. |
| <result> : one value to be evaluated at the end of the execution of the |
| form; the value is returned. |
| <lvalue> : local variable/parameter, global variable, or an indexed lvalue. |
| useded EBNF operators: |
| { } iteration |
| [ ] option |
| |
| forms: |
| ({#', <body> <result>}) |
| ({#'? { <test> <result> } [ <result> ] }) |
| ({#'?! { <test> <result> } [ <result> ] }) |
| ({#'&& { test } }) |
| ({#'|| { test } }) |
| ({#'while <test> <result> <body>}) loop while test evaluates non-zero. |
| ({#'do <body> <test> <result>}) loop till test evaluates zero. |
| ({#'= { <lvalue> <value> } }) assignment |
| other assignment operators work too. |
| |
| lisp similars: |
| #', progn |
| #'? cond |
| #'&& and |
| #'|| or |
| #'while do /* but lisp has more syntactic candy here */ |
| #'= setq |
| |
| A parameter / local variable 'foo' is referenced as 'foo , a global |
| variable as ({#'foo}) . In lvalue positions (assignment), you need not |
| enclose global variable closures in arrays. |
| |
| Call by reference parameters are given with ({#'&, <lvalue>}) |
| |
| Some special efuns: |
| #'[ indexing |
| #'[< indexing from the end |
| #'negate unary - |
| |
| Unbound lambda closures |
| ======================= |
| |
| These closures are not bound to any object. They are created with the efun |
| unbound_lambda() . They cannot contain references to global variables, and |
| all lfun closures are inserted as is, since there is no native object for |
| this closure. |
| You can bind and rebind unbound lambda closures to an object with efun |
| bind_lambda() You need to bind it before it can be called. Ordinary objects |
| can obly bind to themselves, binding to other objects causes a privilege |
| violation(). |
| The point is that previous_object for calls done from inside the closure |
| will reflect the object doing bind_lambda(), and all object / uid based |
| security will also refer to this object. |
| |
| |
| The following is mostly vapourware. |
| Well, another application would be that some things in the driver can be, |
| sort of, microprogrammed. |
| The master object could set some hooks in inaugurate_master(), like creating |
| the code for move_object(), using a primitive low_move_object() or |
| __move_object() or such. All calls of init(), exit(), etc. can thus be |
| controlled on mudlib level. |
| The driver would do an implicit bind_lambda() to the victim when the closure |
| is used. |
| |
| e.g. |
| ({#'?, ({#'=, 'ob, ({#'first_inventory, 'destination}) }), |
| ({#'do, |
| ({#'call_other, 'ob, "init"}), |
| ({#'=, 'ob, ({#'next_inventory, 'ob}) }), 0 }) |
| }) |
| |
| or |
| |
| ({#'filter_objects, ({#'all_inventory, 'destination}), "init"}) |
| /* Won't show init failures due to move/destruct */ |
| |
| is equivalent to |
| |
| if (ob = first_inventory(destination) ) { |
| do { |
| ob->init(); |
| } while(ob = next_inventory(ob) ); |
| } |
| |
| and it's speed is mainly determined by the call_other. Thus, it shouldn't be |
| noticably slower than the current C code in move_object(). |
| |