MG Mud User | 88f1247 | 2016-06-24 23:31:02 +0200 | [diff] [blame^] | 1 | Closure Guide for LPC |
| 2 | |
| 3 | Table of Contents |
| 4 | |
| 5 | 1 Indroduction, Overview and Efun-Closures |
| 6 | |
| 7 | 2 Lfun-, Inline and Lambda-Closures |
| 8 | 2.1 Lfun-Closures |
| 9 | 2.2 Inline-Closures |
| 10 | 2.3 Lambda-Closures |
| 11 | 2.3.1 Advantages of Lambda-Closures |
| 12 | 2.3.2 Free Variables in Lambda-Closure Constructs |
| 13 | 2.3.3 Special Efun-Closures and Operator-Closures for Lambdas |
| 14 | 2.4 Closures with Strange Names |
| 15 | 2.5 Operator-Closures |
| 16 | 2.6 Variable-Closures |
| 17 | |
| 18 | 3 Examples |
| 19 | 3.1 Lfun-Closure |
| 20 | 3.2 Lambda-Closure |
| 21 | |
| 22 | 1 Introduction, Overview and Efun-Closures |
| 23 | |
| 24 | A closure is a pointer to a function. That means that it is data like an |
| 25 | int or a string are. It may be assigned to a variable or given to anoth- |
| 26 | er function as argument. |
| 27 | |
| 28 | To create a closure that points to an efun like write() you can write |
| 29 | the name of the efun prepended with "hash-tick": #'. #'write is a clo- |
| 30 | sure that points to the efun write(). |
| 31 | |
| 32 | I very often put parentheses around such a closure-notation because |
| 33 | otherwise my editor gets confused by the hashmark: (#'write). This is |
| 34 | especially of interest within lambda-closures (see below). |
| 35 | |
| 36 | A closure can be evaluated (which means that the function it points to |
| 37 | is called) using the efuns funcall() or apply(), which also allow to |
| 38 | give arguments to the function. Example: |
| 39 | |
| 40 | funcall(#'write,"hello"); |
| 41 | |
| 42 | This will result in the same as write("hello"); alone. The string |
| 43 | "hello" is given as first (and only) argument to the function the clo- |
| 44 | sure #'write points to. |
| 45 | |
| 46 | The return value of the function the closure points to is returned by |
| 47 | the efun funcall() or apply(). (Since write() always returns 0 the re- |
| 48 | turn value of the example above will be 0.) |
| 49 | |
| 50 | What are closures good for? With closures you can make much more univer- |
| 51 | sally usable functions. A good example is the function filter(). |
| 52 | It gets an array and a closure as arguments. Then it calls the function |
| 53 | the closure points to for each element of the array: |
| 54 | |
| 55 | filter(({ "bla","foo","bar" }),#'write); |
| 56 | |
| 57 | This will call write("bla"), write("foo") and write("bar") in any order; |
| 58 | the order is undefined. |
| 59 | (In the current implementation the given closure is evaluated for all |
| 60 | elements from the first to the last, so the output will be "blafoobar".) |
| 61 | |
| 62 | Furthermore the efun filter() examines the return value of each |
| 63 | call of the function the closure points to (the return value of the |
| 64 | write()s). If the value is true (not 0) then this element is put into |
| 65 | another array which filter() builds up. If the return value is |
| 66 | false (== 0) then this element is _not_ put into this array. When all |
| 67 | calls are done the slowly built up array is returned. Thus, |
| 68 | filter() filters from the given array all elements that the given |
| 69 | closure evaluates "true" for and returns an array of those. (The array |
| 70 | given to filter() itself is _not_ changed!) |
| 71 | |
| 72 | A more sensical example for filterwould be this: |
| 73 | |
| 74 | x = filter(users(),#'query_is_wizard); |
| 75 | |
| 76 | users() is an efun that gets no arguments and returns an array of all |
| 77 | logged in players (wizards and players). query_is_wizard() is a |
| 78 | simul_efun that gets an object as first (and only) argument and returns |
| 79 | true (1) if this object is a wizard and 0 otherwise. |
| 80 | |
| 81 | So, for each element of the array returned by users() the function |
| 82 | query_is_wizard() is called and only those for which 1 was returned are |
| 83 | collected into the result and then put into the variable x. |
| 84 | |
| 85 | We now have all logged in wizards stored as array in the variable x. |
| 86 | |
| 87 | Another example: We want to filter out all numbers that are greater than |
| 88 | 42 from the array a of integers: |
| 89 | |
| 90 | x = filter(({ 10,50,30,70 }),#'>,42); |
| 91 | |
| 92 | (x will now be ({ 50,70 }).) |
| 93 | |
| 94 | Here two things are new: first: we create a closure that points to an |
| 95 | operator; second: we use the possibility to give extra arguments to |
| 96 | filter(). |
| 97 | |
| 98 | Like all efuns the usual operators can be pointed to with a closure by |
| 99 | prepending #' to them. funcall(#'>,4,5) is exactly the same as (4>5). |
| 100 | |
| 101 | The extra arguments given as third to last argument (as many as you |
| 102 | like) to filter() are given as second to last argument to the |
| 103 | function pointed to by the closure each time it is called. |
| 104 | |
| 105 | Thus we now call (({ 10,50,30,70 })[0]>42), (({ 10,50,30,70 })[1]>42) ... |
| 106 | (which is (10>42), (50>42) ...) and return an array of all elements this |
| 107 | returns true for and store it into x. |
| 108 | |
| 109 | If you want to create a closure to an efun of which you have the name |
| 110 | stored in a string you can create such an efun-closure with the efun |
| 111 | symbol_function(): |
| 112 | |
| 113 | symbol_function("write") // this will return #'write |
| 114 | |
| 115 | funcall(symbol_function("write"),"foobar"); // == write("foobar"); |
| 116 | |
| 117 | This function does not very often occur in normal code but it is very |
| 118 | useful for tool-programming (eg the robe uses symbol_function() to allow |
| 119 | you call any efun you give). |
| 120 | |
| 121 | 2 Lfun- and Lambda-Closures |
| 122 | |
| 123 | Very often the possibilities closures to efuns offer are not sufficient |
| 124 | for the purpose one has. In nearly all cases three possibilities exist in |
| 125 | such cases: use an lfun- or inline-closure, or a lambda-closure. |
| 126 | |
| 127 | 2.1 Lfun-Closures |
| 128 | |
| 129 | The first possibility is rather easy: like with the efun-closures you |
| 130 | can create a pointer to a function in the same object you are by using |
| 131 | the #' to prepend it to a function name of a function declared above. |
| 132 | Example: |
| 133 | |
| 134 | status foo(int x) { |
| 135 | return ((x*2) > 42); |
| 136 | } |
| 137 | |
| 138 | int *bar() { |
| 139 | return filter(({ 10,50,30,70 }),#'foo); |
| 140 | } |
| 141 | |
| 142 | Thus, #'foo is used like there was an efun of this name and doing the |
| 143 | job that is done in foo(). |
| 144 | |
| 145 | 2.2 Inline Closure |
| 146 | |
| 147 | Inline closures are a variant of lfun closures, the difference being |
| 148 | that the function text is written right where the closure is used, |
| 149 | enclosed in a pair of '(:' and ':)'. The compiler will then take care |
| 150 | of creating a proper lfun and lfun-closure. The arguments passed to |
| 151 | such an inline closure are accessible by position: $1 would be the |
| 152 | first argument, $2 the second, and so on. With this, the |
| 153 | above example would read: |
| 154 | |
| 155 | int * bar() { |
| 156 | return filter(({ 10,50,30,70 }), (: ($1 * 2) > 42 :)); |
| 157 | } |
| 158 | |
| 159 | or alternatively: |
| 160 | |
| 161 | int * bar() { |
| 162 | return filter(({ 10,50,30,70 }), (: return ($1 * 2) > 42; :)); |
| 163 | } |
| 164 | |
| 165 | The difference between the two versions is that in the first form the text |
| 166 | of the inline closure must be an expression only, whereas in the second |
| 167 | form any legal statement is allowed. The compiler distinguishes the two |
| 168 | forms by the last character before the ':)': if it's a ';' or '}', the |
| 169 | compiler treats the closure as statement(s), otherwise as expression. |
| 170 | |
| 171 | Inline closures may also nested, so that the following (not very useful) |
| 172 | example is legal, too: |
| 173 | |
| 174 | return filter( ({ 10, 50, 30, 70 }) |
| 175 | , (: string *s; |
| 176 | s = map(users(), (: $1->query_name() :)); |
| 177 | return s[random(sizeof(s))] + ($1 * 2); |
| 178 | :)); |
| 179 | |
| 180 | The notation of inline closures is modelled after the MudOS functionals, |
| 181 | but there are a few important differences in behaviour. |
| 182 | |
| 183 | |
| 184 | 2.3 Lambda-Closures |
| 185 | |
| 186 | Lambda-Closures take the idea of 'define it where you use it' one step |
| 187 | further. On first glance they may look like inline closures with an uglier |
| 188 | notation, but they offer a few increased possibilities. But first things |
| 189 | first. |
| 190 | |
| 191 | The efun lambda() creates a function temporarily and returns a closure |
| 192 | pointing to this function. lambda() therefor gets two arrays as |
| 193 | arguments, the first is a list of all arguments the function shall expect |
| 194 | and the second array is the code of the function (in a more or less |
| 195 | complicated form; at least not in C- or LPC-syntax). The closure #'foo |
| 196 | from the example above could be notated as lambda-closure: |
| 197 | |
| 198 | lambda(({ 'x }),({ (#'>), |
| 199 | ({ (#'*),'x,2 }), |
| 200 | 42 |
| 201 | })) |
| 202 | |
| 203 | Now, the first argument is ({ 'x }), an array of all arguments the |
| 204 | function shall expect: 1 argument (called 'x) is expected. Notice the |
| 205 | strange notation for this argument with one single leading tick. Like |
| 206 | The hash-tick to denote closures the leading tick is used to denote |
| 207 | things called "symbols". They do not differ much from strings and if |
| 208 | you do not want to have a deeper look into closures you can leave it |
| 209 | this way. |
| 210 | |
| 211 | The second argument is an array. The first element of such an array |
| 212 | must be an efun- or an lfun-closure, the further elements are the |
| 213 | arguments for the function this closure points to. If such an argu- |
| 214 | ment is an array, it is treated alike; the first element must be a |
| 215 | closure and the remaining elements are arguments (which of course |
| 216 | also might be arrays ...). |
| 217 | |
| 218 | This leads to a problem: sometimes you want to give an array as an |
| 219 | argument to a function. But arrays in an array given to lambda() are |
| 220 | interpreted as code-arrays. To allow you to give an array as an argu- |
| 221 | ment within an array given to lambda(), you can use the function |
| 222 | quote() to make your array to a quoted array (a quoted array is for |
| 223 | an array what a symbol is for a string): |
| 224 | |
| 225 | lambda(0,({ (#'sizeof), |
| 226 | quote(({ 10,50,30,70 })) |
| 227 | })) |
| 228 | |
| 229 | For array constants, you can also use a single quote to the same |
| 230 | effect: |
| 231 | |
| 232 | lambda(0,({ (#'sizeof), |
| 233 | '({ 10,50,30,70 }) |
| 234 | })) |
| 235 | |
| 236 | This lambda-closure points to a function that will return 4 (it will |
| 237 | call sizeof() for the array ({ 10,50,30,70 })). Another thing: if |
| 238 | we want to create a function that expects no arguments, we can give |
| 239 | an empty array as first argument to lambda() but we can give 0 as |
| 240 | well to attain this. This is just an abbreviation. |
| 241 | |
| 242 | Lambda-closure constructs can become quite large and hard to read. The |
| 243 | larger they become the harder the code is to read and you should avoid |
| 244 | extreme cases. Very often the possibility to use an lfun or an inline |
| 245 | instead of a large lambda shortens the code dramatically. Example: |
| 246 | |
| 247 | status foo(object o) { |
| 248 | return environment(o)->query_level()>WL_APPRENTICE; |
| 249 | } |
| 250 | |
| 251 | x=filter(a,#'foo); |
| 252 | |
| 253 | does the same as |
| 254 | |
| 255 | x=filter(a,lambda(({ 'o }), |
| 256 | ({ (#'>), |
| 257 | ({ (#'call_other), |
| 258 | ({ (#'environment),'o }), |
| 259 | "query_level" |
| 260 | }), |
| 261 | WL_APPRENTICE |
| 262 | }))); |
| 263 | |
| 264 | (Note that the syntax with the arrow "->" for call_other()s cannot be |
| 265 | used, #'-> does not exist. You have to use #'call_other for this and |
| 266 | give the name of the lfun to be called as a string.) |
| 267 | |
| 268 | This example also demonstrates the two disadvantages of lambda closures. |
| 269 | First, they are very difficult to read, even for a simple example like |
| 270 | this. Second, the lambda closure is re-created everytime the |
| 271 | filter() is executed, even though the created code is always the |
| 272 | same. |
| 273 | |
| 274 | 'Why use lambdas at all then?' you may ask now. Well, read on. |
| 275 | |
| 276 | |
| 277 | 2.3.1 Advantages of Lambda Closures |
| 278 | |
| 279 | The advantages of lambdas stem from the fact that they are created |
| 280 | at runtime from normal arrays. |
| 281 | |
| 282 | This means that the behaviour of a lambda can be made dependant on data |
| 283 | available only at runtime. For example: |
| 284 | |
| 285 | closure c; |
| 286 | c = lambda(0, ({#'-, ({ #'time }), time() }) ); |
| 287 | |
| 288 | Whenever you now call this closure ('funcall(c)') it will return the |
| 289 | elapsed time since the closure was created. |
| 290 | |
| 291 | The second advantage of lambdas is that the arrays from which they |
| 292 | are compiled can be constructed at runtime. Imagine a customizable prompt |
| 293 | which can be configured to display the time, the environment, or both: |
| 294 | |
| 295 | mixed code; |
| 296 | |
| 297 | code = ({ "> " }); |
| 298 | if (user_wants_time) |
| 299 | code = ({ #'+, ({ #'ctime }), code }); |
| 300 | if (user_wants_environment) |
| 301 | code = ({ #'+, ({#'to_string, ({#'environment, ({#'this_player }) }) }) |
| 302 | , code }); |
| 303 | set_prompt(lambda(0, code)); |
| 304 | |
| 305 | |
| 306 | 2.3.2 Free Variables in Lambda-Closure Constructs |
| 307 | |
| 308 | You can use local variables in lambda constructs without declaring |
| 309 | them, just use them. The only limitation is that you at first have |
| 310 | to assign something to them. Give them as symbols like you do with |
| 311 | the arguments. This feature does not make much sense without the use |
| 312 | of complexer flow controlling features described below. |
| 313 | |
| 314 | The closure #'= is used to assign a value to something (like the |
| 315 | LPC-operator = is). |
| 316 | |
| 317 | 2.3.3 Special Efun-Closures and Operator-Closures for Lambdas |
| 318 | |
| 319 | There are some special closures that are supposed to be used only |
| 320 | within a lambda construct. With them you can create nearly all code |
| 321 | you can with regular LPC-code like loops and conditions. |
| 322 | |
| 323 | #'? acts like the "if" statement in LPC. The first argument is the |
| 324 | condition, the second is the code to be executed if the condition |
| 325 | returns true. The following arguments can also be such couples of |
| 326 | code-arrays that state a condition and a possible result. If at |
| 327 | the end there is a single argument, it is used as the else-case |
| 328 | if no condition returned true. |
| 329 | |
| 330 | lambda(({ 'x }),({ (#'?), // if |
| 331 | ({ (#'>),'x,5 }), // (x > 5) |
| 332 | ({ (#'*),'x,2 }), // result is x * 2; |
| 333 | ({ (#'<),'x,-5 }), // else if (x < -5) |
| 334 | ({ (#'/),'x,2 }), // result is x/2; |
| 335 | 'x // else result is x; |
| 336 | })) |
| 337 | |
| 338 | #'?! is like the #'? but it negates all conditions after evaluation |
| 339 | and thus is like an ifnot in LPC (if there were one). |
| 340 | |
| 341 | #', (which looks a bit strange) is the equivalent of the comma-operator |
| 342 | in LPC and says: evaluate all arguments and return the value of |
| 343 | the last. It is used to do several things inside a lambda-closure. |
| 344 | |
| 345 | lambda(({ 'x }),({ (#',), // two commas necessary! |
| 346 | // one for the closure and one as |
| 347 | // delimiter in the array |
| 348 | ({ (#'write),"hello world!" }), |
| 349 | ({ (#'say),"Foobar." }) |
| 350 | })) |
| 351 | |
| 352 | #'while acts like the LPC statement "while" and repeats executing one |
| 353 | code-array while another returns true. |
| 354 | #'while expects two or more arguments: the condition as first |
| 355 | argument, then the result the whole expression shall have after |
| 356 | the condition turns false (this is in many cases of no interest) |
| 357 | and as third to last argument the body of the loop. |
| 358 | |
| 359 | lambda(0,({ (#',), // several things to do ... |
| 360 | ({ (#'=),'i,0 }), // i is a local variable of this |
| 361 | // lambda-closure and is |
| 362 | // initialized with 0 now. |
| 363 | ({ (#'while), |
| 364 | ({ (#'<),'i,10 }), // condition: i < 10 |
| 365 | 42, // result is not interesting, |
| 366 | // but we must give one |
| 367 | ({ (#'write),'i }), // give out i |
| 368 | ({ (#'+=),'i,1 }) // increase i |
| 369 | }) |
| 370 | })) |
| 371 | |
| 372 | The function this closure points to will give out the |
| 373 | numbers from 0 to 9 and then return 42. |
| 374 | |
| 375 | #'do is like the do-while statement in LPC and is very much like the |
| 376 | #'while. The difference is that #'while tests the condition al- |
| 377 | ready before the body is evaluated for the first time, this means |
| 378 | that the body might not be evaluated even once. #'do evaluates |
| 379 | the body first and then the condition, thus the body is evaluated |
| 380 | at least one time. |
| 381 | Furthermore, the arguments for #'do are changed in order. #'do |
| 382 | expects as first to last but two the body of the loop, then the |
| 383 | condition (as last-but-one'th argument) and the result value as |
| 384 | last argument. So #'do must have at least two arguments: the |
| 385 | condition and the result. |
| 386 | |
| 387 | lambda(0,({ (#',), // several things to do ... |
| 388 | ({ (#'=),'i,0 }), // i is a local variable of this |
| 389 | // lambda-closure and is initialized |
| 390 | // with 0 now. |
| 391 | ({ (#'do), |
| 392 | ({ (#'write),'i }), // give out i |
| 393 | ({ (#'+=),'i,1 }) // increase i |
| 394 | ({ (#'<),'i,10 }), // condition: i < 10 |
| 395 | 42 // result is not interesting |
| 396 | }) |
| 397 | })) |
| 398 | |
| 399 | NOTE: There is no #'for in LPC, you should use #'while for this. |
| 400 | |
| 401 | #'foreach is like the foreach() statement in LPC. It evaluates one or |
| 402 | more bodies repeatedly for every value in a giving string, array |
| 403 | or mapping. The result of the closure is 0. |
| 404 | |
| 405 | #'foreach expects two or more arguments: |
| 406 | - a single variable symbol, or an array with several variable |
| 407 | symbols |
| 408 | - the value to iterate over |
| 409 | - zero or more bodes to evaluate in each iteration. |
| 410 | |
| 411 | The single values retrieved from the given value are assigned |
| 412 | one after another to the variable(s), then the bodies are executed |
| 413 | for each assignment. |
| 414 | |
| 415 | lambda(0, ({#'foreach, 'o, ({#'users}) |
| 416 | , ({#'call_other, 'o, "die" }) |
| 417 | })); |
| 418 | |
| 419 | lambda(0, ({#'foreach, ({'k, 'v}), ({ ...mapping...}) |
| 420 | , ({#'printf, "%O:%O\n", 'k, 'v }) |
| 421 | })); |
| 422 | |
| 423 | #'return gets one argument and acts like the "return" statement in LPC |
| 424 | in the function that is created by lambda(). It aborts the |
| 425 | execution of this function and returns the argument. |
| 426 | |
| 427 | lambda(0,({ (#'while),// loop |
| 428 | 1, // condition is 1 ==> endles loop |
| 429 | 42, // return value (which will never be used) |
| 430 | ({ (#'write),"grin" }) |
| 431 | ({ (#'?!), // ifnot |
| 432 | ({ (#'random),10 }), // (random(10)) |
| 433 | ({ (#'return),100 }) // return 100; |
| 434 | }) |
| 435 | })) |
| 436 | |
| 437 | This function will enter an endles loop that will in each |
| 438 | turn give out "grin" and if random(10) returns 0 (which will |
| 439 | of course happen very soon) it will leave the function with |
| 440 | "return 100". The value 42 that is given as result of the |
| 441 | loop would be returned if the condition would evaluate to 0 |
| 442 | which cannot be. (1 is never 0 ;-) |
| 443 | |
| 444 | #'break is used like the "break" statement in LPC and aborts the exe- |
| 445 | cution of loops and switches. |
| 446 | It must not appear outside a loop or switch of the lambda |
| 447 | closure itself, it cannot abort the execution of the function |
| 448 | the closure points to! |
| 449 | |
| 450 | lambda(0,({ (#'?), |
| 451 | ({ (#'random),2 }), |
| 452 | ({ (#'break) }), // this will cause the error |
| 453 | // "Unimplemented operator break |
| 454 | // for lambda()" |
| 455 | "random was false!" |
| 456 | })); |
| 457 | |
| 458 | You can use ({ #'return,0 }) instead of ({ #'break }) in such |
| 459 | cases. |
| 460 | |
| 461 | #'continue is used like the "continue" statement in LPC and jumps to |
| 462 | the end of the current loop and continues with the loop |
| 463 | condition. |
| 464 | |
| 465 | #'default may be used within a #'switch-construct but be careful! |
| 466 | To call symbol_function("default") (which is done usually |
| 467 | by tools that allow closure-creation) might crash the |
| 468 | driver! So please do only use it within your LPC-files. |
| 469 | (NOTE: This driver bug is fixed somewhere below 3.2.1@131.) |
| 470 | |
| 471 | #'.. may be used within a #'switch-construct but is not implemented |
| 472 | yet (3.2.1@131). But #'[..] works well instead of it. |
| 473 | |
| 474 | #'switch is used to create closures which behave very much like the |
| 475 | switch-construct in LPC. To understand the following you |
| 476 | should already know the syntax and possibilities of the |
| 477 | latter one (which is mightier than the C-version of switch). |
| 478 | |
| 479 | I will confront some LPC versions and the corresponding clo- |
| 480 | sure versions below. |
| 481 | |
| 482 | LPC: Closure: |
| 483 | switch (x) { lambda(0,({ (#'switch), x, |
| 484 | case 5: ({ 5 }), |
| 485 | return "five"; ({ (#'return),"five" }), |
| 486 | (#',), |
| 487 | case 6..9: ({ 6, (#'[..]), 9 }), |
| 488 | return "six to nine"; ({ (#'return), |
| 489 | "six to nine" }), |
| 490 | (#',), |
| 491 | case 1: ({ 1 }), |
| 492 | write("one"); ({ (#'write),"one" }), |
| 493 | // fall through (#',), |
| 494 | case 2: ({ 2, |
| 495 | case 10: 10 }), |
| 496 | return "two or ten"; ({ (#'return), |
| 497 | "two or ten" }), |
| 498 | (#',), |
| 499 | case 3..4: ({ 3, (#'[..]), 4 }), |
| 500 | write("three to four"); ({ (#'write), |
| 501 | "three to four" }), |
| 502 | break; // leave switch (#'break), |
| 503 | default: ({ (#'default) }), |
| 504 | write("something else"); ({ (#'write), |
| 505 | "something else" }), |
| 506 | break; (#'break) |
| 507 | } })) |
| 508 | |
| 509 | #'&& evaluates the arguments from the first on and stops if one evalu- |
| 510 | ates to 0 and returns 0. If none evaluates to 0 it returns the |
| 511 | result of the last argument. |
| 512 | |
| 513 | #'|| evaluates the arguments from the first on and stops if one evalu- |
| 514 | ates to true (not 0) and returns it. If all evaluate to 0 it |
| 515 | returns 0. |
| 516 | |
| 517 | #'catch executes the closure given as argument, but catches any |
| 518 | runtime error (see catch(E)). Optionally the symbols 'nolog, |
| 519 | 'publish and 'reserve may be given as additional arguments to |
| 520 | modify the behaviour of the catch. |
| 521 | |
| 522 | #'sscanf acts similar to how a funcall would, but passes the third |
| 523 | and following arguments as lvalues, that is, values which can |
| 524 | be assigned to. |
| 525 | |
| 526 | #'= and the #'<op>= variants are also special because the first |
| 527 | argument has to be an lvalue. |
| 528 | |
| 529 | 2.4 Closures with Strange Names |
| 530 | |
| 531 | #'negate is the unary minus that returns -x for the argument x. |
| 532 | |
| 533 | map(({ 1,2,3 }),#'negate) |
| 534 | |
| 535 | This returns ({ -1,-2,-3 }). |
| 536 | |
| 537 | #'[ is used for the things that in LPC are done with |
| 538 | the []-operator (it indexes an array or a mapping). |
| 539 | |
| 540 | lambda(0,({ #'[,quote(({ 10,50,30,70 })),2 })) ==> 30 |
| 541 | lambda(0,({ #'[,([ "x":10;50, "y":30;70 ]),"x",1 })) ==> 50 |
| 542 | |
| 543 | #'[< is the same as #'[ but counts the elements from the |
| 544 | end (like indexing with [] and the "<"). |
| 545 | |
| 546 | #'[..] returns a subarray of the argument from the one |
| 547 | given index to the other given index, both counted from the |
| 548 | beginning of the array. |
| 549 | |
| 550 | #'[..<] |
| 551 | #'[<..] |
| 552 | #'[<..<] same as above, but the indexes are counted from the end, |
| 553 | |
| 554 | lambda(0,({ #'[..<], |
| 555 | quote(({ 0,1,2,3,4,5,6,7 })),2,3 |
| 556 | })) |
| 557 | |
| 558 | This will return ({ 2,3,4,5 }). |
| 559 | #'[.. |
| 560 | #'[<.. same as above, but only the first index is given, the |
| 561 | subarray will go till the end of the original (like with |
| 562 | [x..]). |
| 563 | |
| 564 | #'({ is used to create arrays (as with ({ }) in LPC). All arguments |
| 565 | become the elements of the array. |
| 566 | |
| 567 | lambda(0,({ #'({, |
| 568 | ({ (#'random),10 }), |
| 569 | ({ (#'random),50 }), |
| 570 | ({ (#'random),30 }), |
| 571 | ({ (#'random),70 }) |
| 572 | })) |
| 573 | |
| 574 | This returns ({ random(10),random(50),random(30),random(70) }). |
| 575 | |
| 576 | #'([ is used to create mappings out of single entries (with seve- |
| 577 | ral values) like the ([ ]) in LPC. Very unusual is the fact |
| 578 | that this closure gets arrays as argument that are not eval- |
| 579 | uated, although they are not quoted. |
| 580 | |
| 581 | lambda(0,({ #'([, |
| 582 | ({ "x",1,2,3 }), |
| 583 | ({ "y",4,5,6 }) |
| 584 | })); |
| 585 | |
| 586 | This returns ([ "x": 1;2;3, |
| 587 | "y": 4;5;6 ]). |
| 588 | |
| 589 | However, the elements of the arrays are evaluated as lambda |
| 590 | expressions, so if you want to create a mapping from values |
| 591 | evaluated at call time, write them as lambda closures: |
| 592 | |
| 593 | lambda(0, ({ #'([, ({ 1, ({ #'ctime }) }) }) ) |
| 594 | |
| 595 | will return ([ 1: <result of ctime() at call time ]). |
| 596 | |
| 597 | Arrays can be put into the mapping by quoting: |
| 598 | |
| 599 | lambda(0, ({ #'([, ({ 1, '({ 2 }) }) }) ) |
| 600 | |
| 601 | will return ([ 1: ({ 2 }) ]) |
| 602 | |
| 603 | |
| 604 | #'[,] is nearly the same as #'[. The only difference |
| 605 | shows up if you want to index a mapping with a width |
| 606 | greater than 1 (with more than just one value per |
| 607 | key) directly with funcall(). Example: |
| 608 | funcall(#'[,([ 0:1;2, 3:4;5 ]),0,1) |
| 609 | This will not work. Use #'[,] and it will |
| 610 | work. If you want to use it in a lambda closure you |
| 611 | do not have to use #'[,] and #'[ will |
| 612 | do fine. On the other hand, #'[,] cannot |
| 613 | work with arrays, so in nearly all cases use #'[ |
| 614 | and just in the described special case, use |
| 615 | #'[,]. |
| 616 | This is a strange thing and I deem it a bug, so it |
| 617 | might change in the future. |
| 618 | |
| 619 | 2.5 Operator-Closures |
| 620 | |
| 621 | Most of the closures that are used for things which are done by opera- |
| 622 | tors are in fact not operator-closures but efun-closures. But there are |
| 623 | a few which do not have the state of efun-closures but are called |
| 624 | "operator-closures". #'return is an example, a complete list of them is |
| 625 | given below. |
| 626 | |
| 627 | These closures cannot be called directly using funcall() or apply() (or |
| 628 | other efuns like filter()), but must appear only in lambda-con- |
| 629 | structs. |
| 630 | |
| 631 | funcall(#'return,4); // does not work! This will raise an |
| 632 | // Uncallable-closure error. |
| 633 | funcall(lambda(0, // this is a correct example |
| 634 | ({ (#'return),4 }) |
| 635 | )); |
| 636 | |
| 637 | All operator-closures: |
| 638 | #'&& |
| 639 | #'|| |
| 640 | #', |
| 641 | #'? |
| 642 | #'?! |
| 643 | #'= |
| 644 | #'<op>= |
| 645 | #'++ |
| 646 | #'-- |
| 647 | #'break |
| 648 | #'catch |
| 649 | #'continue |
| 650 | #'default |
| 651 | #'do |
| 652 | #'foreach |
| 653 | #'return |
| 654 | #'sscanf |
| 655 | #'switch |
| 656 | #'while |
| 657 | #'({ |
| 658 | #'([ |
| 659 | |
| 660 | #'.. is very likely to be an operator closure too, but since it is |
| 661 | not implemented yet, I cannot say for sure. |
| 662 | |
| 663 | 2.6 Variable-Closures |
| 664 | |
| 665 | All object-global variables might be "closured" by prepending a #' to |
| 666 | them to allow access and/or manipulation of them. So if your object has |
| 667 | a global variable x you can use #'x within a closure. |
| 668 | |
| 669 | Normally you will treat those expressions like lfun-closures: put them |
| 670 | into an array to get the value: |
| 671 | |
| 672 | object.c: |
| 673 | int x; |
| 674 | int foo() { |
| 675 | return lambda(0,({ (#'write),({ (#'x) }) })); |
| 676 | } |
| 677 | |
| 678 | Anybody who now calls object->foo() will get a closure which will, when |
| 679 | evaluated, write the actual value of object's global variable x. |
| 680 | |
| 681 | Variable closures do not accept arguments. |
| 682 | |
| 683 | 3 Examples |
| 684 | |
| 685 | In this section I will give and explain some examples coming out of |
| 686 | praxis. If the explanation seems to be in some cases too detailed this |
| 687 | can be explained by the trial to allow the reader to read the examples |
| 688 | section first ;-) |
| 689 | |
| 690 | 3.1 Lfun-Closure |
| 691 | |
| 692 | An item with a complex long-description like a watch that shall always |
| 693 | show the actual time will usually base upon the complex/item-class and |
| 694 | give an lfun-closure as argument to the set_long()-method. |
| 695 | |
| 696 | watch.c: |
| 697 | inherit "complex/item"; |
| 698 | |
| 699 | string my_long() { |
| 700 | return ("The watch is small and has a strange otherworldly" |
| 701 | " aura about it.\n" |
| 702 | "The current time is: "+ctime()+".\n"); |
| 703 | } |
| 704 | |
| 705 | void create() { |
| 706 | set_short("a little watch"); |
| 707 | set_id(({ "watch","little watch" })); |
| 708 | set_long(#'my_long); // the lfun-closure to the lfun my_long() |
| 709 | } |
| 710 | |
| 711 | 3.2 Lambda-Closure |
| 712 | |
| 713 | The example from 3.1 can also be written using a lambda-closure. |
| 714 | |
| 715 | watch.c: |
| 716 | inherit "complex/item"; |
| 717 | |
| 718 | void create() { |
| 719 | set_short("a little watch"); |
| 720 | set_id(({ "watch","little watch" })); |
| 721 | set_long(lambda(0,({ (#'+), |
| 722 | "The watch is small and has a strange" |
| 723 | " otherworldly aura about it.\n" |
| 724 | "The current time is: ", |
| 725 | ({ (#'+), |
| 726 | ({ (#'ctime) }), |
| 727 | ".\n" |
| 728 | }) |
| 729 | }))); |
| 730 | } |