Dokumentation zu: closures-macbeth(LPC)

HR Image

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);

	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
   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:

   // will dump the variables of ob to /dump.o
     closure save;
     save = unbound_lambda( ({ }), ({ #'save_object, "/open/dump" }) );

   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
   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_array(),
   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.

Start » Magierhandbuch » Docu » LPC » Closures-macbeth Letzte Generierung: 25.04.2021, 01:58
Email an:
Valid HTML 4.01!