| 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 &&, || 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> |
|---|