Dokumentation zu: closures-example(LPC)

HR Image


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)


Start » Magierhandbuch » Docu » LPC » Closures-example Letzte Generierung: 25.04.2021, 01:58
Email an: mud@wl.mud.de
Valid HTML 4.01!