| CONCEPT |
| closures example |
| |
| DESCRIPTION |
| This document contains small examples of the usage of |
| (lambda-)closures. For technical details see the closures(LPC) |
| doc. For hints when to use which type of closure, see the end |
| of this doc. |
| |
| |
| Many Muds use 'details' to add more flavour. 'Details' are |
| items which can be looked at, but are not implemented as own |
| objects, but instead simulated by the environment. |
| Lets assume that the function |
| |
| AddDetail(string keyword, string|closure desc) |
| |
| adds the detail 'keyword' to the room, which, when look at, |
| returns the string 'desc' resp. the result of the execution of |
| closure 'desc' as the detail description to the player. |
| |
| Now imagine that one wants to equip a room with magic runes, |
| which read as 'Hello <playername>!\n" when looked at. |
| Obviously |
| |
| AddDetail("runes", sprintf( "Hello %s!\n" |
| , this_player()->QueryName())); |
| |
| is not sufficient, as the 'this_player()' is executed to early |
| and just once: for the player loading the room. |
| |
| The solution is to use closures. First, the solution using |
| lfun-closures: |
| |
| private string _detail_runes () { |
| return sprintf("Hello %s!\n", this_player()->QueryName()); |
| } |
| ... |
| AddDetail("runes", #'_detail_runes); |
| |
| or with an inline closure: |
| |
| AddDetail("runes" |
| , (: sprintf("Hello %s!\n", this_player()->QueryName()) :) |
| ); |
| |
| |
| Simple? Here is the same code, this time as lambda-closure: |
| |
| AddDetail( "runes" |
| , lambda(0 |
| , ({#'sprintf, "Hello %s!\n" |
| , ({#'call_other, ({#'this_player}) |
| , "QueryName" }) |
| }) |
| )); |
| |
| Why the extra ({ }) around '#'this_player'? #'this_player |
| alone is just a symbol, symbolizing the efun this_player(), |
| but call_other() needs an object as first argument. Therefore, |
| the #'this_player has to be interpreted as function to |
| evaluate, which is enforced by enclosing it in ({ }). The same |
| reason also dictates the enclosing of the whole #'call_other |
| expression into ({ }). |
| Note also the missing #'return: it is not needed. The result |
| of a lambda-closure is the last value computed. |
| |
| |
| Another example: Task is to reduce the HP of every living in a |
| room by 10, unless the result would be negative. |
| Selecting all livings in a room is simply |
| |
| filter(all_inventory(room), #'living) |
| |
| The tricky part is to reduce the HP. Again, first the |
| lfun-closure solution: |
| |
| private _reduce_hp (object liv) { |
| int hp; |
| hp = liv->QueryHP(); |
| if (hp > 10) |
| liv->SetHP(hp-10); |
| } |
| ... |
| |
| map( filter(all_inventory(room), #'living) |
| , #'_reduce_hp) |
| |
| or as an inline closure: |
| |
| map( filter(all_inventory(room), #'living) |
| , (: int hp; |
| hp = liv->QueryHP(); |
| if (hp > 10) |
| liv->SetHP(hp - 10); |
| :) ); |
| |
| Both filter() and map() pass the actual array item |
| being filtered/mapped as first argument to the closure. |
| |
| Now, the lambda-closure solution: |
| |
| map( filter(all_inventory(room), #'living) |
| , lambda( ({ 'liv }) |
| , ({'#, , ({#'=, 'hp, ({#'call_other, 'liv, "QueryHP" }) }) |
| , ({#'?, ({#'>, 'hp, 10 }) |
| , ({#'call_other, 'liv, "SetHP" |
| , ({#'-, 'hp, 10 }) |
| }) |
| }) |
| }) |
| ) // of lambda() |
| ); |
| |
| It is worthy to point out how local variables like 'hp' are |
| declared in a lambda-closure: not at all. They are just used |
| by writing their symbol 'hp . Same applies to the closures |
| parameter 'liv . |
| The lambda-closure solution is not recommended for three |
| reasons: it is complicated, does not use the powers of |
| lambda(), and the lambda() is recompiled every time this |
| statement is executed! |
| |
| |
| So far, lambda-closures seem to be just complicated, and in |
| fact: they are. Their powers lie elsewhere. |
| |
| Imagine a computation, like for skill resolution, which |
| involves two object properties multiplied with factors and |
| then added. |
| The straightforward solution would be a function like: |
| |
| int Compute (object obj, string stat1, int factor1 |
| , string stat2, int factor2) |
| { |
| return call_other(obj, "Query"+stat1) * factor1 |
| + call_other(obj, "Query"+stat2) * factor2; |
| } |
| |
| Each call to Compute() involves several operations (computing |
| the function names and resolving the call_other()s) which in |
| fact need to be done just once. Using lambda-closures, one can |
| construct and compile a piece of code which behaves like a |
| Compute() tailored for a specific stat/factor combination: |
| |
| closure ConstructCompute (object obj, string stat1, int factor1 |
| , string stat2, int factor2) |
| { |
| mixed code; |
| |
| // Construct the first multiplication. |
| // The symbol_function() creates a symbol for the |
| // lfun 'Query<stat1>', speeding up later calls. |
| // Note again the extra ({ }) around the created symbol. |
| |
| code = ({#'*, ({ symbol_function("Query"+stat1, obj) }) |
| , factor1 }); |
| |
| // Construct the second multiplication, and the addition |
| // of both terms. |
| |
| code = ({#'+, code |
| , ({#'*, ({ symbol_function("Query"+stat2, obj) }) |
| , factor2 }) |
| }); |
| |
| // Compile the code and return the generated closure. |
| return lambda(0, code); |
| } |
| |
| Once the closure is compiled, |
| |
| str_dex_fun = ConstructCompute(obj, "Str", 10, "Dex", 90); |
| |
| it can be used with a simple 'funcall(str_dex_fun)'. |
| |
| |
| DESCRIPTION -- When to use which closure? |
| First, a closure is only then useful if it needn't to live any |
| longer than the object defining it. Reason: when the defining |
| object gets destructed, the closure will vanish, too. |
| |
| Efun-, lfun- and inline closures should be used where useful, as they |
| mostly do the job and are easy to read. The disadvantage of lfun- and |
| inline closures is that they make a replace_program() impossible |
| - but since such objects tend to not being replaceable at all, this is |
| no real loss. |
| |
| Lambda closures are needed if the actions of the closure are |
| heavily depending on some data available only at runtime, like |
| the actual inventory of a certain player. |
| If you use lfun-closures and find yourself shoving around |
| runtime data in arguments or (gasp!) global variables, it is |
| time to think about using a lambda-closure, compiling the |
| value hard into it. |
| The disadvantages of lambda closures are clear: they are damn |
| hard to read, and each lambda() statement requires extra time to |
| compile the closure. |
| |
| |
| SEE ALSO |
| closures(LPC), closure_guide(LPC), closures-abstract(LPC) |