blob: f50fd359bcf393d0b34e0679f42fae63af6f0b22 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001CONCEPT
2 closures example
3
4DESCRIPTION
5 This document contains small examples of the usage of
6 (lambda-)closures. For technical details see the closures(LPC)
7 doc. For hints when to use which type of closure, see the end
8 of this doc.
9
10
11 Many Muds use 'details' to add more flavour. 'Details' are
12 items which can be looked at, but are not implemented as own
13 objects, but instead simulated by the environment.
14 Lets assume that the function
15
16 AddDetail(string keyword, string|closure desc)
17
18 adds the detail 'keyword' to the room, which, when look at,
19 returns the string 'desc' resp. the result of the execution of
20 closure 'desc' as the detail description to the player.
21
22 Now imagine that one wants to equip a room with magic runes,
23 which read as 'Hello <playername>!\n" when looked at.
24 Obviously
25
26 AddDetail("runes", sprintf( "Hello %s!\n"
27 , this_player()->QueryName()));
28
29 is not sufficient, as the 'this_player()' is executed to early
30 and just once: for the player loading the room.
31
32 The solution is to use closures. First, the solution using
33 lfun-closures:
34
35 private string _detail_runes () {
36 return sprintf("Hello %s!\n", this_player()->QueryName());
37 }
38 ...
39 AddDetail("runes", #'_detail_runes);
40
41 or with an inline closure:
42
43 AddDetail("runes"
44 , (: sprintf("Hello %s!\n", this_player()->QueryName()) :)
45 );
46
47
48 Simple? Here is the same code, this time as lambda-closure:
49
50 AddDetail( "runes"
51 , lambda(0
52 , ({#'sprintf, "Hello %s!\n"
53 , ({#'call_other, ({#'this_player})
54 , "QueryName" })
55 })
56 ));
57
58 Why the extra ({ }) around '#'this_player'? #'this_player
59 alone is just a symbol, symbolizing the efun this_player(),
60 but call_other() needs an object as first argument. Therefore,
61 the #'this_player has to be interpreted as function to
62 evaluate, which is enforced by enclosing it in ({ }). The same
63 reason also dictates the enclosing of the whole #'call_other
64 expression into ({ }).
65 Note also the missing #'return: it is not needed. The result
66 of a lambda-closure is the last value computed.
67
68
69 Another example: Task is to reduce the HP of every living in a
70 room by 10, unless the result would be negative.
71 Selecting all livings in a room is simply
72
73 filter(all_inventory(room), #'living)
74
75 The tricky part is to reduce the HP. Again, first the
76 lfun-closure solution:
77
78 private _reduce_hp (object liv) {
79 int hp;
80 hp = liv->QueryHP();
81 if (hp > 10)
82 liv->SetHP(hp-10);
83 }
84 ...
85
86 map( filter(all_inventory(room), #'living)
87 , #'_reduce_hp)
88
89 or as an inline closure:
90
91 map( filter(all_inventory(room), #'living)
92 , (: int hp;
93 hp = liv->QueryHP();
94 if (hp > 10)
95 liv->SetHP(hp - 10);
96 :) );
97
98 Both filter() and map() pass the actual array item
99 being filtered/mapped as first argument to the closure.
100
101 Now, the lambda-closure solution:
102
103 map( filter(all_inventory(room), #'living)
104 , lambda( ({ 'liv })
105 , ({'#, , ({#'=, 'hp, ({#'call_other, 'liv, "QueryHP" }) })
106 , ({#'?, ({#'>, 'hp, 10 })
107 , ({#'call_other, 'liv, "SetHP"
108 , ({#'-, 'hp, 10 })
109 })
110 })
111 })
112 ) // of lambda()
113 );
114
115 It is worthy to point out how local variables like 'hp' are
116 declared in a lambda-closure: not at all. They are just used
117 by writing their symbol 'hp . Same applies to the closures
118 parameter 'liv .
119 The lambda-closure solution is not recommended for three
120 reasons: it is complicated, does not use the powers of
121 lambda(), and the lambda() is recompiled every time this
122 statement is executed!
123
124
125 So far, lambda-closures seem to be just complicated, and in
126 fact: they are. Their powers lie elsewhere.
127
128 Imagine a computation, like for skill resolution, which
129 involves two object properties multiplied with factors and
130 then added.
131 The straightforward solution would be a function like:
132
133 int Compute (object obj, string stat1, int factor1
134 , string stat2, int factor2)
135 {
136 return call_other(obj, "Query"+stat1) * factor1
137 + call_other(obj, "Query"+stat2) * factor2;
138 }
139
140 Each call to Compute() involves several operations (computing
141 the function names and resolving the call_other()s) which in
142 fact need to be done just once. Using lambda-closures, one can
143 construct and compile a piece of code which behaves like a
144 Compute() tailored for a specific stat/factor combination:
145
146 closure ConstructCompute (object obj, string stat1, int factor1
147 , string stat2, int factor2)
148 {
149 mixed code;
150
151 // Construct the first multiplication.
152 // The symbol_function() creates a symbol for the
153 // lfun 'Query<stat1>', speeding up later calls.
154 // Note again the extra ({ }) around the created symbol.
155
156 code = ({#'*, ({ symbol_function("Query"+stat1, obj) })
157 , factor1 });
158
159 // Construct the second multiplication, and the addition
160 // of both terms.
161
162 code = ({#'+, code
163 , ({#'*, ({ symbol_function("Query"+stat2, obj) })
164 , factor2 })
165 });
166
167 // Compile the code and return the generated closure.
168 return lambda(0, code);
169 }
170
171 Once the closure is compiled,
172
173 str_dex_fun = ConstructCompute(obj, "Str", 10, "Dex", 90);
174
175 it can be used with a simple 'funcall(str_dex_fun)'.
176
177
178DESCRIPTION -- When to use which closure?
179 First, a closure is only then useful if it needn't to live any
180 longer than the object defining it. Reason: when the defining
181 object gets destructed, the closure will vanish, too.
182
183 Efun-, lfun- and inline closures should be used where useful, as they
184 mostly do the job and are easy to read. The disadvantage of lfun- and
185 inline closures is that they make a replace_program() impossible
186 - but since such objects tend to not being replaceable at all, this is
187 no real loss.
188
189 Lambda closures are needed if the actions of the closure are
190 heavily depending on some data available only at runtime, like
191 the actual inventory of a certain player.
192 If you use lfun-closures and find yourself shoving around
193 runtime data in arguments or (gasp!) global variables, it is
194 time to think about using a lambda-closure, compiling the
195 value hard into it.
196 The disadvantages of lambda closures are clear: they are damn
197 hard to read, and each lambda() statement requires extra time to
198 compile the closure.
199
200
201SEE ALSO
202 closures(LPC), closure_guide(LPC), closures-abstract(LPC)