Dokumentation zu: closures(LPC)

HR Image


CONCEPT
        closures

NOTE
        This is the official man page concerning closures. If you find
        it hard to read maybe the Closure Guide is an easier introduction
        for you: See closure_guide(LPC) and closures-example(LPC).

DESCRIPTION
        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.

        Inline closures are a variant of lfun closures, the difference
        being that the function text is written right where the
        closure is used.  Since they are pretty powerful by
        themselves, inline closures have their own manpage.

        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, loopbody1, ..., loopbodyN, loopcond, loopresult })

        will evaluate the loopbodies until loopcond evaluates to 0 and
        then return the value of loopresult. Symbols may be used as
        variables, of course.

                ({ #'while, loopcond, loopresult, loopbody1, ..., loopbodyN })

        works similar but evaluates loopcond before the loopbodies.

        The foreach() loop also exists:

                ({ #'foreach, 'var, expr, loopbody1, ..., loopbodyN })
                ({ #'foreach, ({ 'var1, ..., 'varN}) , expr
                                        , loopbody1, ..., loopbodyN })


        Now on to a couple of tricky things:

        a) How do I write down an array within a lambda closure to
           avoid interpretation as a subclosure?

           ({ #'member, ({ "abc", "xyz" }), 'x }) 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, '({ "abc", "xyz" }), 'x }).
           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, quote( ({ "abc", "xyz" }), 'x ) })

        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.
        #'[..] : gets an array and two numbers
                and returns a sub-array.
        #'[..<] : same as above but the second index is
                interpreted as counted from the left end.
        #'[<..]  and
        #'[<..<] : should be clear now.
        #'[.. : takes only one index and returns the sub-
                array from this index to the end.
        #'[<.. : same as above but the index is interpreted
                as counted from the left end.
        #'({ : puts all arguments into an array.
        #'([ : gets an unquoted (!) array which must include
                at least one element as argument and returns a mapping of
                the width of the given array's size with one entry that
                contains the first element as key and the other elements
                as values to the key.

        #'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.


        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.


AUTHOR
        MacBeth, Amylaar, Hyp

SEE ALSO
        closures-abstract(LPC), closures-example(LPC), closure_guide(LPC)
        inline-closures(LPC)


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