root/trunk/docsrc/lazy-evaluation.dd

Revision 659, 6.4 kB (checked in by andrei, 4 years ago)

Changed ddoc extensions to dd and changed linux.mak accordingly

  • Property svn:eol-style set to native
Line 
1 Ddoc
2
3 $(D_S Lazy Evaluation of Function Arguments,
4
5 <center>
6 $(I by Walter Bright, $(LINK http://www.digitalmars.com/d))
7 </center>
8
9
10 $(P Lazy evaluation is the technique of not evaluating an expression
11 unless and until the result of the expression is required.
12 The &amp;&amp;, || and ?: operators are the conventional way to
13 do lazy evaluation:
14 )
15
16 ---
17 void test(int* p)
18 {
19     if (p && p[0])
20     ...
21 }
22 ---
23
24 $(P The second expression $(TT p[0]) is not evaluated unless $(TT p)
25 is not $(B null).
26 If the second expression was not lazily evaluated, it would
27 generate a runtime fault if $(TT p) was $(B null).
28 )
29
30 $(P While invaluable, the lazy evaluation operators have significant
31 limitations. Consider a logging function, which logs
32 a message, and can be turned on and off at runtime based on a global
33 value:
34 )
35
36 ---
37 void log(char[] message)
38 {
39     if (logging)
40     fwritefln(logfile, message);
41 }
42 ---
43
44 $(P Often, the message string will be constructed at runtime:
45 )
46
47 ---
48 void foo(int i)
49 {
50     log("Entering foo() with i set to " ~ toString(i));
51 }
52 ---
53
54 $(P While this works, the problem is that the building of the message
55 string happens regardless of whether logging is enabled or not.
56 With applications that make heavy use of logging, this can become
57 a terrible drain on performance.
58 )
59
60 $(P One way to fix it is by using lazy evaluation:
61 )
62
63 ---
64 void foo(int i)
65 {
66     if (logging) log("Entering foo() with i set to " ~ toString(i));
67 }
68 ---
69
70 $(P but this violates encapsulation principles by exposing the details
71 of logging to the user. In C, this problem is often worked around
72 by using a macro:
73 )
74
75 $(CCODE
76 #define LOG(string)  (logging && log(string))
77 )
78
79 $(P but that just papers over the problem. Preprocessor macros have
80 well known shortcomings:)
81
82 $(UL
83 $(LI The $(TT logging) variable is exposed in the user's namespace.)
84 $(LI Macros are invisible to symbolic debuggers.)
85 $(LI Macros are global only, and cannot be scoped.)
86 $(LI Macros cannot be class members.)
87 $(LI Macros cannot have their address taken, so cannot be passed indirectly
88      like functions can.)
89 )
90
91 $(P A robust solution would be
92 a way to do lazy evaluation of function parameters. Such a way
93 is possible in the D programming language using a delegate parameter:
94 )
95
96 ---
97 void log(char[] delegate() dg)
98 {
99     if (logging)
100     fwritefln(logfile, dg());
101 }
102
103 void foo(int i)
104 {
105     log( { return "Entering foo() with i set to " ~ toString(i); });
106 }
107 ---
108
109 $(P Now, the string building expression only gets evaluated if logging
110 is true, and encapsulation is maintained. The only trouble is that
111 few are going to want to wrap expressions with $(TT { return $(I exp); }).
112 )
113
114 $(P So D takes it one small, but crucial, step further
115 (suggested by Andrei Alexandrescu).
116 Any expression
117 can be implicitly converted to a delegate that returns either $(TT void) or
118 the type of the expression.
119 The delegate declaration is replaced by the $(TT lazy) storage class
120 (suggested by Tomasz Stachowiak).
121 The functions then become:
122 )
123
124 ---
125 void log(lazy char[] dg)
126 {
127     if (logging)
128     fwritefln(logfile, dg());
129 }
130
131 void foo(int i)
132 {
133     log("Entering foo() with i set to " ~ toString(i));
134 }
135 ---
136
137 $(P which is our original version, except that now the string is not
138 constructed unless logging is turned on.
139 )
140
141 $(P Any time there is a repeating pattern seen in code, being able to
142 abstract out that pattern and encapsulate it means we can reduce the
143 complexity of the code, and hence bugs. The most common example of
144 this is the function
145 itself.
146 Lazy evaluation enables encapsulation of a host of other patterns.
147 )
148
149 $(P For a simple example, suppose an expression is to be evaluated $(I count)
150 times. The pattern is:
151 )
152
153 ---
154 for (int i = 0; i < count; i++)
155    exp;
156 ---
157
158 $(P This pattern can be encapsulated in a function using lazy evaluation:
159 )
160
161 ---
162 void dotimes(int count, lazy void exp)
163 {
164     for (int i = 0; i < count; i++)
165        exp();
166 }
167 ---
168
169 $(P It can be used like:
170 )
171
172 ---
173 void foo()
174 {
175     int x = 0;
176     dotimes(10, writef(x++));
177 }
178 ---
179
180 $(P which will print:
181 )
182
183 $(CONSOLE
184 0123456789
185 )
186
187 $(P More complex user defined control structures are possible.
188 Here's a method to create a switch like structure:
189 )
190
191 ---
192 bool scase(bool b, lazy void dg)
193 {
194     if (b)
195     dg();
196     return b;
197 }
198
199 /* Here the variadic arguments are converted to delegates in this
200    special case.
201  */
202 void cond(bool delegate()[] cases ...)
203 {
204     foreach (c; cases)
205     {   if (c())
206         break;
207     }
208 }
209 ---
210
211 $(P which can be used like:
212 )
213
214 ---
215 void foo()
216 {
217     int v = 2;
218     cond
219     (
220     scase(v == 1, writefln("it is 1")),
221     scase(v == 2, writefln("it is 2")),
222     scase(v == 3, writefln("it is 3")),
223     scase(true,   writefln("it is the default"))
224     );
225 }
226 ---
227
228 $(P which will print:
229 )
230
231 $(CONSOLE
232 it is 2
233 )
234
235 $(P Those familiar with the Lisp programming language will notice some
236 intriguing parallels with Lisp macros.
237 )
238
239 $(P For a last example, there is the common pattern:
240 )
241
242 ---
243 Abc p;
244 p = foo();
245 if (!p)
246     throw new Exception("foo() failed");
247 p.bar();    // now use p
248 ---
249
250 $(P Because throw is a statement, not an expression, expressions that
251 need to do this need to be broken up into multiple statements,
252 and extra variables are introduced.
253 (For a thorough treatment of this issue, see Andrei Alexandrescu and
254 Petru Marginean's paper
255 $(LINK2 http://erdani.org/publications/cuj-06-2003.html, Enforcements)).
256 With lazy evaluation, this can all be encapsulated into a single
257 function:
258 )
259
260 ---
261 Abc Enforce(Abc p, lazy char[] msg)
262 {
263     if (!p)
264     throw new Exception(msg());
265     return p;
266 }
267 ---
268
269 $(P and the opening example above becomes simply:
270 )
271
272 ---
273 Enforce(foo(), "foo() failed").bar();
274 ---
275
276 $(P and 5 lines of code become one. Enforce can be improved by making it a
277 template function:
278 )
279
280 ---
281 T Enforce(T)(T p,  lazy char[] msg)
282 {
283     if (!p)
284     throw new Exception(msg());
285     return p;
286 }
287 ---
288
289 <h2>Conclusion</h2>
290
291 $(P Lazy evaluation of function arguments dramatically extends the expressive
292 power of functions. It enables the encapsulation into functions of many
293 common coding patterns and idioms that previously were too clumsy or
294 impractical to do.
295 )
296
297 <h2>Acknowledgements</h2>
298
299     $(P I gratefully acknowledge the inspiration and assistance
300     of Andrei Alexandrescu, Bartosz Milewski, and David Held.
301     The D community helped a lot with much constructive
302     criticism, such as the thread starting with
303     Tomasz Stachowiak in $(NG_digitalmars_D 41633).
304     )
305
306 )
307
308 Macros:
309     TITLE=LazyEvaluationOfFunctionArguments
310     WIKI=LazyEvaluation
311
312     NG_digitalmars_D = <a href="http://www.digitalmars.com/pnews/read.php?server=news.digitalmars.com&group=digitalmars.D&artnum=$0">D/$0</a>
Note: See TracBrowser for help on using the browser.