Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 315 lines (256 sloc) 6.567 kB
4542ef3 @andralex moved docsrc where it belongs
andralex authored
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
995e538 @andralex Removed or commented out obsolete pages
andralex authored
24 $(P The second expression $(D p[0]) is not evaluated unless $(D p)
4542ef3 @andralex moved docsrc where it belongs
andralex authored
25 is not $(B null).
26 If the second expression was not lazily evaluated, it would
995e538 @andralex Removed or commented out obsolete pages
andralex authored
27 generate a runtime fault if $(D p) was $(B null).
4542ef3 @andralex moved docsrc where it belongs
andralex authored
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
995e538 @andralex Removed or commented out obsolete pages
andralex authored
83 $(LI The $(D logging) variable is exposed in the user's namespace.)
4542ef3 @andralex moved docsrc where it belongs
andralex authored
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
995e538 @andralex Removed or commented out obsolete pages
andralex authored
111 few are going to want to wrap expressions with $(D { return $(I exp); }).
4542ef3 @andralex moved docsrc where it belongs
andralex authored
112 )
113
114 $(P So D takes it one small, but crucial, step further
115 (suggested by Andrei Alexandrescu).
116 Any expression
995e538 @andralex Removed or commented out obsolete pages
andralex authored
117 can be implicitly converted to a delegate that returns either $(D void) or
4542ef3 @andralex moved docsrc where it belongs
andralex authored
118 the type of the expression.
995e538 @andralex Removed or commented out obsolete pages
andralex authored
119 The delegate declaration is replaced by the $(D lazy) storage class
4542ef3 @andralex moved docsrc where it belongs
andralex authored
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
e5f97f1 @WalterBright try again to commit
WalterBright authored
311 CATEGORY_ARTICLES=$0
4542ef3 @andralex moved docsrc where it belongs
andralex authored
312
313 NG_digitalmars_D = <a href="http://www.digitalmars.com/pnews/read.php?server=news.digitalmars.com&group=digitalmars.D&artnum=$0">D/$0</a>
314
Something went wrong with that request. Please try again.