forked from DlangRen/Programming-in-D
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mixin.d
486 lines (368 loc) · 11.5 KB
/
mixin.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
Ddoc
$(DERS_BOLUMU $(IX mixin) Mixins)
$(P
Mixins are for $(I mixing in) generated code into the source code. The mixed in code may be generated as a template instance or a $(C string).
)
$(H5 $(IX template mixin) Template mixins)
$(P
We have seen in the $(LINK2 /ders/d.en/templates.html, Templates) and $(LINK2 /ders/d.en/templates_more.html, More Templates) chapters that templates define code as a pattern, for the compiler to generate actual instances from that pattern. Templates can generate functions, structs, unions, classes, interfaces, and any other legal D code.
)
$(P
Template mixins insert instantiations of templates into the code by the $(C mixin) keyword:
)
---
mixin $(I a_template)!($(I template_parameters))
---
$(P
As we will see in the example below, the $(C mixin) keyword is used in the definitions of template mixins as well.
)
$(P
The instantiation of the template for the specific set of template parameters is inserted into the source code right where the $(C mixin) keyword appears.
)
$(P
For example, let's have a template that defines both an array of edges and a pair of functions that operate on those edges:
)
---
$(CODE_NAME EdgeArrayFeature)$(HILITE mixin) template EdgeArrayFeature(T, size_t count) {
T[count] edges;
void setEdge(size_t index, T edge) {
edges[index] = edge;
}
void printEdges() {
writeln("The edges:");
foreach (i, edge; edges) {
writef("%s:%s ", i, edge);
}
writeln();
}
}
---
$(P
That template leaves the type and number of array elements flexible. The instantiation of that template for $(C int) and $(C 2) would be mixed in by the following syntax:
)
---
$(HILITE mixin) EdgeArrayFeature!(int, 2);
---
$(P
For example, the $(C mixin) above can insert the two-element $(C int) array and the two functions that are generated by the template right inside a $(C struct) definition:
)
---
$(CODE_NAME Line)$(CODE_XREF EdgeArrayFeature)struct Line {
mixin EdgeArrayFeature!(int, 2);
}
---
$(P
As a result, $(C Line) ends up defining a member array and two member functions:
)
---
$(CODE_XREF Line)import std.stdio;
void main() {
auto line = Line();
line.setEdge(0, 100);
line.setEdge(1, 200);
line.printEdges();
}
---
$(P
The output:
)
$(SHELL
The edges:
0:100 1:200
)
$(P
Another instantiation of the same template can be used e.g. inside a function:
)
---
$(CODE_XREF EdgeArrayFeature)struct Point {
int x;
int y;
}
void main() {
$(HILITE mixin) EdgeArrayFeature!($(HILITE Point), 5);
setEdge(3, Point(3, 3));
printEdges();
}
---
$(P
That $(C mixin) inserts an array and two local functions inside $(C main()). The output:
)
$(SHELL
The edges:
0:Point(0, 0) 1:Point(0, 0) 2:Point(0, 0) 3:Point(3, 3) 4:Point(0, 0)
)
$(H6 $(IX local import) $(IX import, local) Template mixins must use local imports)
$(P
Mixing in template instantiations $(I as is) can cause problems about the modules that the template itself is making use of: Those modules may not be available at the $(C mixin) site.
)
$(P
Let's consider the following module named $(C a). Naturally, it would have to import the $(C std.string) module that it is making use of:
)
---
module a;
$(HILITE import std.string;) $(CODE_NOTE wrong place)
mixin template A(T) {
string a() {
T[] array;
// ...
return format("%(%s, %)", array);
}
}
---
$(P
However, if $(C std.string) is not imported at the actual $(C mixin) site, then the compiler would not be able to find the definition of $(C format()) at that point. Let's consider the following program that imports $(C a) and tries to mix in $(C A!int) from that module:
)
---
import a;
void main() {
mixin A!int; $(DERLEME_HATASI)
}
---
$(SHELL
Error: $(HILITE undefined identifier format)
Error: mixin deneme.main.A!int error instantiating
)
$(P
For that reason, the modules that template mixins use must be imported in local scopes:
)
---
module a;
mixin template A(T) {
string a() {
$(HILITE import std.string;) $(CODE_NOTE right place)
T[] array;
// ...
return format("%(%s, %)", array);
}
}
---
$(P
As long as it is inside the template definition, the $(C import) directive above can be outside of the $(C a()) function as well.
)
$(H6 $(IX this, template parameter) Identifying the type that is mixing in)
$(P
Sometimes a mixin may need to identify the actual type that is mixing it in. That information is available through $(I $(C this) template parameters) as we have seen in $(LINK2 /ders/d.en/templates_more.html, the More Templates chapter):
)
---
mixin template MyMixin(T) {
void foo$(HILITE (this MixingType))() {
import std.stdio;
writefln("The actual type that is mixing in: %s",
$(HILITE MixingType).stringof);
}
}
struct MyStruct {
mixin MyMixin!(int);
}
void main() {
auto a = MyStruct();
a.foo();
}
---
$(P
The output of the program shows that the actual type is available inside the template as $(C MyStruct):
)
$(SHELL
The actual type that is mixing in: MyStruct
)
$(H5 $(IX string mixin) String mixins)
$(P
Another powerful feature of D is being able to insert code as $(C string) as long as that string is known at compile time. The syntax of string mixins requires the use of parentheses:
)
---
mixin $(HILITE $(PARANTEZ_AC))$(I compile_time_generated_string)$(HILITE $(PARANTEZ_KAPA))
---
$(P
For example, the $(I hello world) program can be written with a $(C mixin) as well:
)
---
import std.stdio;
void main() {
mixin (`writeln("hello world");`);
}
---
$(P
The string gets inserted as code and the program produces the following output:
)
$(SHELL
hello world
)
$(P
We can go further and insert all of the program as a string mixin:
)
---
mixin (
`import std.stdio; void main() { writeln("hello world"); }`
);
---
$(P
Obviously, there is no need for mixins in these examples, as the strings could have been written as code as well.
)
$(P
The power of string mixins comes from the fact that the code can be generated at compile time. The following example takes advantage of CTFE to generate statements at compile time:
)
---
import std.stdio;
string printStatement(string message) {
return `writeln("` ~ message ~ `");`;
}
void main() {
mixin (printStatement("hello world"));
mixin (printStatement("hi world"));
}
---
$(P
The output:
)
$(SHELL
hello world
hi world
)
$(P
Note that the $(STRING "writeln") expressions are not executed inside $(C printStatement()). Rather, $(C printStatement()) generates code that includes $(C writeln()) expressions that are executed inside $(C main()). The generated code is the equivalent of the following:
)
---
import std.stdio;
void main() {
writeln("hello world");
writeln("hi world");
}
---
$(H5 $(IX name space, mixin) Mixin name spaces)
$(P
It is possible to avoid and resolve name ambiguities in template mixins.
)
$(P
For example, there are two $(C i) variables defined inside $(C main()) in the following program: one is defined explicitly in $(C main) and the other is mixed in. When a mixed-in name is the same as a name that is in the surrounding scope, then the name that is in the surrounding scope gets used:
)
---
import std.stdio;
template Templ() {
$(HILITE int i;)
void print() {
writeln(i); // Always the 'i' that is defined in Templ
}
}
void main() {
$(HILITE int i;)
mixin Templ;
i = 42; // Sets the 'i' that is defined explicitly in main
writeln(i); // Prints the 'i' that is defined explicitly in main
print(); // Prints the 'i' that is mixed in
}
---
$(P
As implied in the comments above, template mixins define a name space for their contents and the names that appear in the template code are first looked up in that name space. We can see this in the behavior of $(C print()):
)
$(SHELL
42
0 $(SHELL_NOTE printed by print())
)
$(P
The compiler cannot resolve name conflicts if the same name is defined by more than one template mixin. Let's see this in a short program that mixes in the same template instance twice:
)
---
template Templ() {
int i;
}
void main() {
mixin Templ;
mixin Templ;
i = 42; $(DERLEME_HATASI)
}
---
$(SHELL
Error: deneme.main.Templ!().i at ... $(HILITE conflicts with)
deneme.main.Templ!().i at ...
)
$(P
To prevent this, it is possible to assign name space identifiers for template mixins and refer to contained names by those identifiers:
)
---
mixin Templ $(HILITE A); // Defines A.i
mixin Templ $(HILITE B); // Defines B.i
$(HILITE A.)i = 42; // ← not ambiguous anymore
---
$(P
String mixins do not have these name space features. However, it is trivial to use a string as a template mixin simply by passing it through a simple wrapper template.
)
$(P
Let's first see a similar name conflict with string mixins:
)
---
void main() {
mixin ("int i;");
mixin ("int i;"); $(DERLEME_HATASI)
i = 42;
}
---
$(SHELL
Error: declaration deneme.main.i is $(HILITE already defined)
)
$(P
One way of resolving this issue is to pass the $(C string) through the following trivial template that effectively converts a string mixin to a template mixin:
)
---
template Templatize(string str) {
mixin (str);
}
void main() {
mixin Templatize!("int i;") A; // Defines A.i
mixin Templatize!("int i;") B; // Defines B.i
A.i = 42; // ← not ambiguous anymore
}
---
$(H5 $(IX operator overloading, mixin) String mixins in operator overloading)
$(P
We have seen in $(LINK2 /ders/d.en/operator_overloading.html, the Operator Overloading chapter) how $(C mixin) expressions helped with the definitions of some of the operators.
)
$(P
In fact, the reason why most operator member functions are defined as templates is to make the operators available as $(C string) values so that they can be used for code generation. We have seen examples of this both in that chapter and its exercise solutions.
)
$(H5 Example)
$(P
($(I $(B Note:) Specifying predicates as strings was used more commonly before the lambda syntax was added to D. Although string predicates as in this example are still used in Phobos, the $(C =>) lambda syntax may be more suitable in most cases.))
)
$(P
Let's consider the following function template that takes an array of numbers and returns another array that consists of the elements that satisfy a specific condition:
)
---
int[] filter($(HILITE string predicate))(in int[] numbers) {
int[] result;
foreach (number; numbers) {
if ($(HILITE mixin (predicate))) {
result ~= number;
}
}
return result;
}
---
$(P
That function template takes the filtering condition as its template parameter and inserts that condition directly into an $(C if) statement as is.
)
$(P
For that condition to choose numbers that are e.g. less than 7, the $(C if) condition should look like the following code:
)
---
if (number < 7) {
---
$(P
The users of $(C filter()) template can provide the condition as a $(C string):
)
---
int[] numbers = [ 1, 8, 6, -2, 10 ];
int[] chosen = filter!$(HILITE "number < 7")(numbers);
---
$(P
Importantly, the name used in the template parameter must match the name of the variable used in the implementation of $(C filter()). So, the template must document what that name should be and the users must use that name.
)
$(P
Phobos uses names consisting of single letters like a, b, n, etc.
)
macros:
SUBTITLE=Mixins
DESCRIPTION=Template mixins and string mixins.
KEYWORDS=d programming language tutorial book mixin
SOZLER=
$(katma)
$(sablon)