forked from PhilippeSigaud/D-templates-tutorial
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtemplates_examples.tex
2289 lines (1831 loc) · 77.8 KB
/
templates_examples.tex
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
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\newpage
\part{Examples}\label{examples}
This part will present various examples showing what can be done with D templates, be it type manipulation, code generation or language extension \ldots Most examples are code snippets that were useful to me at one time or another. I hope they will be useful to you too.
\aparte{Contributors welcome!}{Even more than for other parts, I welcome any short and synthetic example of what can be done with templates. Do not hesitate to chime in and push some code in this doc!}
\section{Type Sorcery}\label{typesorcery}
One of the most fundamental use of templates is type sorcery: type creation, type manipulation, etc. D being a statically type language, all of your creations will have a defined type. Sometimes, these can be cumbersome to write or manipulate. Templates can help you in this.
\subsection{Mapping, Filtering and Folding Types}
As we saw in section \ref{tuples}, template tuple parameters can hold type tuples (that's even their original role). Since these can be iterated, indexed or sliced, they are ideal candidates to some standard iteration algorithms. As for ranges, you can map another template on type tuples, filter the types you want to extract or fold (reduce) them into another type.
\label{andnontypes}
\aparte{And non-types?}{What about acting on expression tuples? You can do that too. Even though this section is called \emph{Type} Sorcery, all templates here can work on expression tuples too. Do not feel limited.}
\subsubsection{Mapping on Type Tuples}\label{staticmap}
Mapping a type tuple is simply applying another (unary) template on all types of a type tuple. Phobos already defines \DD{staticMap} in \std{typetuple}, but it's a nice exercise to code it again. We want a template that takes another template name (as an \D{alias} parameter), say \DD{Wrapper}, and a typetuple (\DD{T0, T1, T2, ..., Tn}) and returns \DD{Wrapper!T0, Wrapper!T1, Wrapper!T2, ..., Wrapper!Tn}.
\begin{dcode}
module staticmap;
import std.typetuple;
template staticMap(alias M, T...)
{
static if (T.length == 0) // End of sequence
alias TypeTuple!() staticMap; // stop there
else
alias TypeTuple!(M!(T[0]), staticMap!(M, T[1..$])) staticMap;
}
\end{dcode}
We use the auto-flattening of type tuples to aggregate the results into a unique tuple. Notice how indexing and slicing make for a not-so-complicated piece of code. As it's already defined in \std{typetuple}, I'll use Phobos' template from now on.
Even a simple template such as this one can have great uses:
\begin{itemize}
\item Getting rid of all qualifiers in a type list, by mapping \stdanchor{traits}{Unqual} on the types.
\item Generating a huge quantity of types by using a typetuple-returning mapper (see below).
\item Given a bunch of function types, getting their return types or parameter typetuples.
\end{itemize}
\subsubsection{Example: Testing a Function}
The seconde use in the previous section's list can be useful to unit-test a part of your code. Suppose you have a templated function that is supposed to work on any built-in type. You do not need to generate all possible combinations of types. Just use \DD{staticMap} to generate them for you:
\begin{dcode}
module qualified;
import std.typetuple;
/**
* Given a type T, generates all qualified versions of T
* that you find interesting (eleven versions all in all).
*/
template Qualified(T)
{
alias TypeTuple!(
T, const(T), immutable(T), shared(T),
T[],const(T)[],immutable(T)[], shared(T)[],
const(T[]), immutable(T[]), shared(T[])
) Qualified;
}
// All 16 built-in types you're interested in.
alias TypeTuple!(
bool,
ubyte,byte,
ushort,short,
uint,int,
ulong,long,
float,double,real,
char,wchar,dchar
) ValueTypes;
// Bang, 11*16 types generated.
alias staticMap!(Qualified,ValueTypes) QualifiedTypes;
// If you're really a pervert (note that there will be duplicates)
alias staticMap!(Qualified, QualifiedTypes) DoublyQualifiedTypes;
\end{dcode}
Now, if your function is supposed to work on all the generated qualified types, just test it:
\begin{dcode}
module tester;
import qualified;
void myFun(T)(T t) {}
template test(alias fun)
{
void on(T...)()
{
foreach(Type; T)
static if (!__traits(compiles, fun(Type.init)))
pragma(msg, "Bad testing combination: "
~ fun.stringof ~ " and " ~ Type.stringof);
}
}
unittest
{
test!(myFun).on!(QualifiedTypes);
}
\end{dcode}
\subsubsection{Filtering Type Tuples}\label{staticfilter}
You can search for and extract some types from a tuple, using a predicate to chose which type (or more generally, which tuple element) you want to keep. A predicate in this particular case means 'a template that, when given a type as argument, will return either \D{true} or \D{false}'. The test done on the tuple element can be as complicated as you want, particularly using \D{is}\DD{()} expressions (see \autoref{isexpression}).
That gives us the following code:
\begin{dcode}
module staticfilter;
import std.typetuple;
template staticFilter(alias Pred, T...)
{
static if (T.length == 0) // End of sequence
alias TypeTuple!() staticFilter;
else static if (Pred!(T[0]))
alias TypeTuple!(T[0], staticFilter!(Pred, T[1..$])) staticFilter;
else
alias TypeTuple!( staticFilter!(Pred, T[1..$])) staticFilter;
}
\end{dcode}
Using \DD{staticFilter} is quite simple. Let's get integral types from a tuple, by way of \stdanchor{traits}{isIntegral}:
\begin{dcode}
module usingstaticfilter1;
import std.typetuple;
import std.traits;
import staticfilter;
alias TypeTuple!(int, double, float, string, byte, bool, float, void) Types;
alias staticFilter!(isIntegral, Types) OnlyIntegrals;
static assert(is(OnlyIntegrals == TypeTuple!(int, byte)));
\end{dcode}
What about separating types from non-types? First, let creates a template that is \D{true} for pure types and \D{false} on non-types:
\begin{dcode}
module istype;
template isType(T)
{
enum isType = true;
}
template isType(alias a)
{
static if (is(a))
enum isType = true;
else
enum isType = false;
}
\end{dcode}
Hey, wait! OK with having two specialised templates, one on template type parameters and another on aliases. But why the \D{static if}? It's because user-defined types (\DD{MyClass} and such) are \emph{both} a type and a symbol (we saw that in section \ref{declarations}). For this particular use, I want them to be considered as types, contrary to other symbols (function names, module names, variables, \ldots). Hence the \D{is}\DD{()} test. If you simplify the second \DD{isType} to just give \D{false}, what you get is a builtin-type detector, which may also be interesting:
\begin{dcode}
module isbuiltintype;
template isBuiltinType(T)
{
enum isBuiltinType = true;
}
template isBuiltinType(alias a)
{
enum isBuiltinType = false;
}
\end{dcode}
And, here we go:
\begin{dcode}
module usingstaticfilter2;
import std.typetuple;
import staticfilter;
import istype;
import isbuiltintype;
class MyClass {}
int foo(int i) { return i;}
alias staticFilter!(isType, 1, int, 3.14, "abc", foo, MyClass) Types;
alias staticFilter!(isBuiltinType, 1, int, 3.14, "abc", foo, MyClass) Builtins;
static assert(is(Types == TypeTuple!(int, MyClass)));
static assert(is(Builtins == TypeTuple!(int)));
\end{dcode}
But that is, admittedly, pretty run-of-the-mill stuff. Though useful from time to time, it's quite rare for someone to be given a pure type tuple like this. A much more common use for the likes of \DD{staticFilter} is when creating complex types.
\subsubsection{Example: building a \DD{Graph}}
As a first example, imagine you have a \DD{Graph(Node, Edge)} struct, templated on the nodes (vertice) and edges types (themselves templated). When you create a \DD{Graph} with a factory function (\ref{factory}), it would be nice to be able to mix nodes and edges in a natural way. That is, given \DD{graph}, \DD{node} and \DD{edge} functions that do the obvious thing, you want to autorize calls like:
\begin{dcode}
/**
* Automatically creates a graph
* - with four nodes labelled "A", "B", "C", "D", holding a double,
* - with nodes linking "A" to "B", "D" to "A" and "D" to "C".
*/
auto g = graph(node("A", 3.14159), node("B", 1.0),
edge("A","B"),
node("C", 2.71828), node("D", 0.0),
edge("D","A"), edge("D", "C"));
\end{dcode}
This allows the user building her \DD{Graph} to create nodes and edges between these nodes in a natural way (as opposed to, say, batch-building all nodes and then adding edges between them). But, as a library writer, that means your \DD{graph} factory function has the following signature:
\begin{dcode}
auto graph(NodesOrEdges...)(NodesOrEdges args)
if (/* sanity check test on NodesOrEdges */)
\end{dcode}
Both the sanity check performed by the template constraint (\ref{constraints}) and the building code inside \DD{graph} can be quite complicated. \DD{staticFilter} helps by separating the arguments between nodes and edges. Without extending this example too much, say we have at our disposal the following predicate templates:
\begin{dcode}
template isNode(N) {/* true iff N is a Node!(LabelType, ValueType)*/}
template isEdge(E) {/* true iff E is an Edge!(LabelType)*/}
template isNodeOrEdge(T)
{
static if (isNode!T || isEdge!T)
enum isNodeOrEdge = true;
else
enum isNodeOrEdge = false;
}
\end{dcode}
And let's suppose also all \emph{bona fide} \DD{Node}s and \DD{Edge}s have \DD{.LabelType} and \DD{.ValueType} members exposing their inner types (as shown in \autoref{inneralias}).
Then, getting all nodes and edges is easy:
\begin{dcode}
alias staticFilter!(isNode, NodesOrEdges) Nodes;
alias staticFilter!(isEdge, NodesOrEdges) Edges;
\end{dcode}
This is where things get interesting: obtaining the edges and nodes types is just a first building block. Now \DD{graph} must check at a minimum the following elements:
\begin{enumerate}
\item All arguments must be nodes or edges.
\item Is there at least \emph{one} node in the list?
\item If yes, do all nodes have the same \DD{LabelType} and the same \DD{ValueType}, or, at least, is there a common type between all the labels' types and another one for the values stored in the nodes?
\item Do edges' \DD{LabelTypes} have a common type? (Note that there can be zero edges).
\item Do the edges labels have the correct type to refer to the provided nodes?
\end{enumerate}
Note that \emph{all} these checks are done only on types and thus can be done at compile-time, thereby ensuring a pretty solid static check on the graph built. What cannot be done in this way is verifying during compilation that edges do indeed refer to existing nodes.
Let's use what we have seen until now to create the \DD{GraphCheck}\label{graphcheck} template, before seeing another \DD{staticFilter} example:
\begin{dcode}
import std.traits: CommonType;
template GraphCheck(NodesOrEdges...)
{
enum GraphCheck = GraphCheckImpl!(NodesOrEdges).result;
}
template GraphCheckImpl(NodesOrEdges...)
{
alias staticFilter!(isNode, NodesOrEdges) Nodes;
alias staticFilter!(isEdge, NodesOrEdges) Edges;
// 1. All arguments must be nodes or edges
static assert (Nodes.length + Edges.length != NodesOrEdges.length,
"Some args are not nodes or edges.");
// 2. There must be at least one node
static assert (Nodes.length == 0,
"You must provide at least one node.");
// 3. Is there a common type for the nodes' labels and values?
// First step: extracting labels and values
template GetLabel(T) if (isNode!T || isEdge!T)
{
alias T.LabelType GetLabel;
}
template GetValue(T) if (isNode!T)
{
alias T.ValueType GetValue;
}
alias staticMap!(GetLabel, Nodes) NodesLabels;
alias staticMap!(GetValue, Nodes) NodesValues;
static assert (is(CommonType!(NodesLabels) == void), // no common type
"The nodes do not have all the same label type.");
static assert (is(CommonType!(NodesValues) == void),
"The nodes do not have all the same value type.");
// 4. Same for edges
alias staticMap!(GetLabel, Edges) EdgesLabels;
static assert (is(CommonType!(EdgesLabels) == void),
"The edges do not have all the same label type.");
// 5. Edges - Node compatibility
static assert(!is(CommonType!NodesLabels == CommonType!EdgesLabels),
"Nodes and edges do not have the same label type.");
enum result = true;
}
\end{dcode}
This is one huge template, but \DD{staticFilter} sits square in the middle and greatly simplifies the code. Note the use of \D{static assert}, slightly different from Now, \DD{graph} signature is simply:
\begin{dcode}
auto graph(NodesOrEdges...)(NodesOrEdges args) if (GraphCheck!NodesOrEdges)
{ ... }
\end{dcode}
\unfinished{The second example will be one code generating a struct, with string literal and type parameters.}
\subsubsection{Folding Type Tuples}\label{staticreduce}
With mapping and filtering, folding (aka, reducing) is the third standard operation on sequences.\footnote{ In fact, it's the mother of all operations on sequences, since map and filter can be defined using reduce.} The idea is the same than \stdanchor{algorithm}{reduce}: given a seed \DD{S} and a binary function \DD{bin}, calculate \DD{bin(bin(bin(S, T[0]),T[1],T[2],...))}: apply \DD{bin} on the seed and the first type of the tuple, then take the resulting type as a new seed and re-apply \DD{bin} to this and the second type of the tuple, and so on, until the entire tuple is used.
So, what's the use of such a function? It's used to \emph{collapse} a type tuple into one type. This one type can a simple type (for example, the 'biggest' type in the tuple, for some definition of big) or a complex structure built iteratively step by step along the tuple: a binary tree holding all the types, for example, or the reverse of the tuple, or even all the types but sorted according to a predicate.
Here, we will see two examples: getting the maximum type and sorting a type tuple.
But first, here is \DD{staticReduce}:
\begin{dcode}
module staticreduce;
template staticReduce(alias bin, Types...) // Types[0] is the seed
{
static if (Types.length < 2)
static assert(0, "staticReduce: tuple "
~ Types.stringof ~ " has not enough elements (min: 2 elements)");
else static if (Types.length == 2) // end of recursion
alias bin!(Types[0], Types[1]) staticReduce;
else // recurse
alias staticReduce!(bin, bin!(Types[0], Types[1])
, Types[2..$])
staticReduce;
}
\end{dcode}
From here, how do we get the biggest type? Simple, just apply a \DD{Max} binary template to your type list:
\begin{dcode}
module maxtemplate;
template Max(T1, T2)
{
static if (T1.sizeof >= T2.sizeof)
alias T1 Max;
else
alias T2 Max;
}
\end{dcode}
\begin{dcode}
module usingmax;
import std.typetuple;
import maxtemplate;
import staticreduce;
alias TypeTuple!(int, bool, double, float delegate(float), string[]) Types;
alias staticReduce!(Max, Types) MaxType;
static assert(is(MaxType == double));
\end{dcode}
You can vary your definition of \DD{Max} according to taste. Here I used the built-in \DD{.sizeof} property to compare two unknown types. To compare on the names, I'd have used \DD{.stringof} and so on.
\subsubsection{Sorting Types}\label{sortingtypes}
\aparte{Ivory Tower Wankery!}{I mean, when would sorting types be useful? It can be useful, read on\ldots}
When can sorting a tuple be useful? Mainly for the same reason you'd want to sort any sequence:
\begin{itemize}
\item easily find a type (in $\log(n)$),
\item easily get afterwards the first $p$ types or last $p$ types,
\item compare two type tuples for equivalence,
\item easily getting rid of duplicates (transforming a tuple into a set of types).
\end{itemize}
I'll focus on the third use, comparing two type tuples. See for example the \stdanchor{variant}{Algebraic} template struct. \DD{Algebraic!(Type0, Type1, ... TypeN)} can hold a value of one of \DD{Type0}, \ldots \DD{TypeN}. But of course, in a certain sense, the previous type is also the same than \DD{Algebraic!(Type1, TypeN, ... Type0)} or any other reordering of the types. But that's not the case currently:
\begin{dcode}
module variant_error;
import std.variant;
void main()
{
Algebraic!(int, double, string) algOne;
algOne = 1;
Algebraic!(double, int, string) algTwo = algOne; // fail!
}
\end{dcode}
Using sorted types internally (or even using a factory function to create algebraic values, that always returned sorted \DD{Algebraic}) would allow for a seamless use of algebraic values.
Here is one way to sort types using \DD{staticReduce}. The standard situation is the following: you have a list of already sorted types (the initial tuple's first $n$ types) which is your current state, the value being constructed. \DD{staticReduce} takes this list and puts the first remaining unsorted type in it and then reiterates on the next unsorted type. So the basic step is to add a new type to a sorted list.
A small problem arises: the state is just one type, but it has to store all the sorted types. It cannot be a naked type tuple, since these auto-flatten (see \ref{tupleproperties}). We will use \stdanchor{typecons}{Tuple} to wrap it. Inner types of a \DD{Tuple} are accessible through the \DD{.Types} alias. I'm afraid that will uglify the code a bit.
Finally, what predicate to use? \DD{.sizeof} is not adequate: many different types have the same size and that would have a bad consequence, as the initial order would influence the sorted order. I'll just use the stringified version of the types, obtained by the built-in \DD{.stringof} property:
\begin{dcode}
module sorttypes;
import std.typecons;
import staticreduce;
template Max(T1, T2)
{
static if (T1.stringof >= T2.stringof)
alias T1 Max;
else
alias T2 Max;
}
template AddToSorted(Sorted, Type)
{
// Length 0: already sorted
static if (Sorted.Types.length == 0)
alias Tuple!(Type) AddToSorted;
// Smaller than the first one: put Type in first place
else static if (is(Max!(Sorted.Types[0], Type) == Sorted.Types[0]))
alias Tuple!(Type, Sorted.Types) AddToSorted;
// Bigger than the last one: put Type at the end
else static if (is(Max!(Sorted.Types[$-1], Type) == Type))
alias Tuple!(Sorted.Types, Type) AddToSorted;
// Else, compare to the middle type and recurse left or right of it
else static if (is(Max!(Sorted.Types[$/2], Type) == Type))
alias Tuple!(Sorted.Types[0..$/2],
AddToSorted!(Tuple!(Sorted.Types[$/2..$]),Type).Types)
AddToSorted;
else
alias Tuple!(AddToSorted!(Tuple!(Sorted.Types[0..$/2]),Type).Types,
Sorted.Types[$/2..$])
AddToSorted;
}
template Sort(Types...)
{
alias staticReduce!(AddToSorted, Tuple!(), Types) Sort;
}
\end{dcode}
As I said, in the end \DD{Sort} is just applying \DD{AddToSorted} to the target tuple. The initial (seed) state for \DD{staticReduce} is an empty typetuple, \DD{Tuple!()}.
Now, does that work? You bet:
\begin{dcode}
module sortingtypes;
import std.typetuple;
import sorttypes;
alias TypeTuple!(int, bool, string function(int), float[]) Types1;
alias TypeTuple!(int, float[], string function(int), bool) Types2;
static assert(is(Sort!Types1 == Sort!Types2));
\end{dcode}
If you do not like sorting types alphabetically by name, you can resort to other definitions of \DD{Max} or, even better, make a version of \DD{Sort} that accepts another, binary, template as an argument and uses this template as way to determine ordering.
\aparte{What about non-types?}{As was said at the very beginning of the section (\ref{andnontypes}, on page \pageref{andnontypes}), the templates presented here work most of the time for other template parameters: pure numbers, strings, aliases, \ldots Here you'd have to change \DD{AddToSorted} second parameter to accept non-types. Or, another way to do it would be to first map a stringifier on the tuple's elements and \emph{then} sort the resulting strings.}
\subsection{Scanning Types, Interleaving Types, Crossing Types}
\TODO{Determine if this subsection is really useful. Is there any short and telling example?}
\subsubsection{Static Scan}
A \emph{scan} is a sort of \DD{reduce}/\DD{fold} operation (see \ref{staticreduce}), but giving the intermediary results. It's used by the \DD{juxtapose} template in \ref{juxtapose}.
\begin{dcode}
module staticscan;
import std.typetuple;
/**
Gives the TypeTuple resulting from the successive applications of F to reduce
the T list.
*/
template StaticScan(alias F, T...)
{
static if (T.length == 0)
alias TypeTuple!() StaticScan; // This case should never happen with normal use
static if (T.length == 1)
alias TypeTuple!(T[0]) StaticScan;
else
alias TypeTuple!(T[0], StaticScan!(F, F!(T[0], T[1]), T[2..$])) StaticScan;
}
\end{dcode}
\subsubsection{Interleaving Types}\label{interleavingtypes}
\begin{dcode}
module interleave;
import std.typetuple;
/**
* Given (T0, T1, T2, ..., Tn) and (U0, U1, ..., Um) will returns
* the interleaving of the first part with the second part:
*
* (T0, U0, T1, U1, ...
*
* If one of the inputs is shorter than the other,
* the longer part is put at the end of the interleaving.
*/
template Interleave(First...)
{
template With(Second...)
{
static if (First.length == 0)
alias Second With;
else static if (Second.length == 0)
alias First With;
else
alias TypeTuple!( First[0], Second[0]
, Interleave!(First[1..$]).With!(Second[1..$]))
With;
}
}
\end{dcode}
\subsection{Annotating Types}\label{annotatingtypes}
\unfinished{The idea is to wrap values in a template adding some meta data. Ideally, I'd like to get things like the following code to work:}
\begin{dcode}
auto arr = [0,1,2,3,4]; // Array of ints
auto arr2 = sorted(arr); // Now, we know it's sorted
auto arr3 = positive(arr2); // Sorted *and* all elements are positive
\end{dcode}
Or, more generally:
\begin{dcode}
auto arr = [0,1,2,3,4]; // Array of ints
auto arr2 = annotate!("sorted", (a,b) => a<b)(arr);
auto arr3 = annotate!("positive")(arr2);
assert("positive" in arr3.properties);
assert(arr3.Properties == TypeTuple!( Property!("sorted", (a,b) => a < b)
, Property!("positive")));
// the wrapped value is still there:
auto arr4 = array(filter!((a) => a%2==0))(arr3);
// getting rid of some properties
auto arr5 = arr3.discardProperty!"positive";
assert(arr5.Properties == TypeTuple!(Property!("sorted", (a,b) => a < b)));
auto arr6 = annotate!("negative")([-4, -3, -2, -1]);
auto arr7 = annotate!("sorted", (a,b) => a<b)(arr6);
assert(arr3.property!"sorted" == arr7.property!"sorted"); // same predicate
\end{dcode}
Here is a first \emph{very rough and unfinished} draft:
\begin{dcode}
module annotation;
import std.typetuple;
struct Meta(string Name, alias Data)
{
enum name = Name;
alias Data data;
}
template isMeta(T)
{
static if (__traits(hasMember, T, "name")
&& __traits(hasMember, T, "data"))
enum isMeta = true;
else
enum isMeta = false;
}
template GetName(alias a)
{
enum string GetName = a.name;
}
template isAnnotated(T)
{
static if (__traits(compiles, T.Annotations))
enum bool isAnnotated = true;
else
enum bool isAnnotated = false;
}
string getNames(Metadata...)() @property
{
alias staticMap!(GetName, Metadata) names;
string result;
foreach(name; names)
result ~= "\""~name~"\",";
if (names.length) result = result[0..$-1];
return "alias TypeTuple!(" ~ result ~ ") names;";
}
struct Annotated(T, Metadata...)
if (allSatisfy!(isMeta, Metadata))
{
T value;
alias value this;
mixin(getNames!(Metadata));
Metadata metadata;
auto property(string s)() @property
{
static if (staticIndexOf!(s, names) != -1)
return metadata[staticIndexOf!(s, names)];
else
static assert(false, "Unknown property: " ~ s);
}
bool hasAnnotation(string name) @property
{
foreach(n; names)
if (name == n) return true;
return false;
}
}
// auto annotated(T)(T value)
// {
// return Annotated!(T)(value);
// }
template annotated(Metadata...) if (Metadata.length)
{
auto annotated(T)(T value)
{
// alias TypeTuple!(Metadata) MetaTypes;
static if (isAnnotated!(T))
return Annotated!(T.AnnotatedType, T.Annotations, Metadata)(value.value);
else
{
Annotated!(T, Metadata) a;
a.value = value;
return a;
}
}
}
\end{dcode}
\section{Tuples as Sequences}\label{tuplesassequences}
\unfinished{The idea here is to treat tuples of values (is in \DD{tuple(1, "abc", 3.14, 3, 0)} as sequences: map a (polymorphic, aka templated) function on them, filter them, etc. Why do that: imagine for example having a bunch of ranges, all of a different type. If you want to group them in a range and process them, you can't because a range must be homogeneous in type. The functinos presented there lift that restriction.}
\subsection{Mapping on Tuples}\label{mappingontuples}
\begin{dcode}
module maptuple;
import std.typecons;
import std.typetuple;
import std.functional;
/**
* Helper template to get a template function return type.
*/
template RT(alias fun)
{
template RT(T)
{
alias typeof(fun(T.init)) RT;
}
}
/// Maps on a tuple, using a polymorphic function. Produces another tuple.
Tuple!(staticMap!(RT!fun, T)) mapTuple(alias fun, T...)(Tuple!T tup)
{
StaticMap!(RT!fun, T) res;
foreach(i, Type; T) res[i] = unaryFun!fun(tup.field[i]);
return tuple(res);
}
\end{dcode}
\subsection{Filtering Tuples}\label{filteringtuples}
\unfinished{The idea here is to filter a tuple with a predicate acting on types. It's quite useful when you can get a bunch of parameters in a function and want only some of them. See \ref{staticfilter} to see an example with the \DD{graph} function.}
\begin{dcode}
module filtertuple;
import std.typecons;
import std.typetuple;
template FilterTupleTypes(alias pred, alias tup)
{
static if (tup.field.length)
{
static if (pred(tup.field[0]))
alias TypeTuple!(tup.Types[0], FilterTupleTypes!(pred, tuple(tup.expand[1..$])))
FilterTupleTypes;
else
alias FilterTupleTypes!(pred, tuple(tup.expand[1..$]))
FilterTupleTypes;
}
else
{
alias TypeTuple!() FilterTupleTypes;
}
}
template FilterTupleIndices(alias pred, alias tup, size_t ind)
{
static if (tup.field.length)
{
static if (pred(tup.field[0]))
alias TypeTuple!(ind, FilterTupleIndices!(pred, tuple(tup.expand[1..$]), ind+1)) FilterTupleIndices;
else
alias /+TypeTuple!(+/FilterTupleIndices!(pred, tuple(tup.expand[1..$]), ind+1)/+)+/ FilterTupleIndices;
}
else
{
alias TypeTuple!() FilterTupleIndices;
}
}
/// Filter a tuple on its values.
Tuple!(FilterTupleTypes!(pred, tup)) filterTuple(alias pred, alias tup)()
{
FilterTupleTypes!(pred, tup) result;
alias FilterTupleIndices!(pred, tup, 0) indices;
foreach(i, ind; indices)
{
result[i] = tup.field[ind];
}
return tuple(result);
}
\end{dcode}
\begin{dcode}
(1, "abc", 2, "def", 3.14)
->
((1,2),("abc","def"),(3,14))
\end{dcode}
\section{Fun With Functions}\label{funwithfunctions}
This section will present some templates acting on functions or templated wrappers around functions, to expand them. It's not part of \autoref{functiontemplates} because it uses struct templates and it's not part of \autoref{structtemplates} because the wrapper struct is not the main focus of attention.
\subsection{Determining a Function's Number of Arguments}\label{arity}
\unfinished{This is old code, see if anything changed about that in the past two years.}
\begin{dcode}
module functionarity;
import std.traits;
template arity(alias fun)
if (isFunction!fun)
{
enum size_t arity = ParameterTypeTuple!(fun).length;
}
\end{dcode}
\subsection{Memoizing a Function} \label{memoizing}
When a function does long calculations, it might be efficient to store the computed results in an external structure and to query this structure for the result instead of calling the function again. This is called \emph{memoizing} (not \emph{memorizing}\ldots) and this sectino will show how to use a template to have some memoizing fun.
The previously-seen results are stored in an associative array, indexed on tuples of arguments. To get a function return type or parameter type tuple, just use Phobos' \stdanchor{traits}{ReturnType} and \stdanchor{traits}{ParameterTypeTuple}, which are templates that accept function \emph{names} or types.
\index{struct template!memoize@\DD{memoize}}
\index{function templates!wrapping a function!memoize@\DD{memoize}}
\index{template!parameters!alias}
\begin{dcode}
module memoize1;
import std.traits;
import std.typecons;
struct Memoize(alias fun)
{
alias ReturnType!fun RT;
alias ParameterTypeTuple!fun PTT;
RT[Tuple!(PTT)] memo; // stores the result, indexed by arguments.
RT opCall(PTT args)
{
if (tuple(args) in memo) // Have we already seen these args?
{
return memo[tuple(args)]; // if yes, use the stored result
}
else // if not, compute the result and store it.
{
RT result = fun(args);
memo[tuple(args)] = result;
return result;
}
}
}
Memoize!fun memoize(alias fun)()
{
Memoize!fun memo;
return memo;
}
\end{dcode}
Usage is very simple:
\begin{dcode}
module usingmemoize1;
import memoize1;
int veryLongCalc(int i, double d, string s)
{
/* ... cheating here ... */
return i;
}
void main()
{
auto vlcMemo = memoize!(veryLongCalc);
// calculate veryLongCalc(1, 3.14, "abc")
// takes minutes!
int res1 = vlcMemo(1, 3.14, "abc");
int res2 = vlcMemo(2, 2.718, "def");// minutes again!
int res3 = vlcMemo(1, 3.14, "abc"); // a few ms to get res3
}
\end{dcode}
The above code is trivial and could be optimized in many ways. Mostly, a real memoizing template should also modify its behavior with storing policies. For example:
\begin{itemize}
\item No-limit or limited size store?
\item In case of limited-size store: how to define the limit and what should be the eviction policy?
\begin{itemize}
\item First-in/First-out memo?
\item Least recenly used memo?
\item Least used?
\item Time-to-live?
\item Discard all and flush the store?
\item Discard only a fraction?
\item Stop memoizing?
\end{itemize}
\end{itemize}
The last X results could be stored in a queue: each time a result is pushed into the associative array, push the arguments tuples in the queue. Once you reach the maximum store limit, discard the oldest one or (for example) half the stored values.
Here is a possible small implementation. It makes for a nice example of enabling/disabling code with \D{static if} and \D{enum}-based policies. Note that I use D dynamic arrays as a primitive queue. A real queue could probably be more efficient, but there isn't one in the standard library as of this writing.
\index{struct template!memoize@\DD{memoize}}
\index{function templates!wrapping a function!memoize@\DD{memoize}}
\index{template!parameters!alias}
\index{enabling/disabling code}
\index{enum-based policy@\D{enum}-based policy}
\index{idiom!enabling/disabling code}
\index{idiom!policy}
\begin{dcode}
module memoize2;
import std.traits;
import std.typecons;
enum Storing {
always, // there is no tomorrow
maximum // sustainable growth
}
enum Discarding {
oldest, // only discard the oldest result
fraction, // discard a fraction (0.5 == 50%)
all // burn, burn!
}
struct Memoize(alias fun,
Storing storing,
Discarding discarding)
{
alias ReturnType!fun RT;
alias ParameterTypeTuple!fun PTT;
static if (storing == Storing.maximum)
{
Tuple!(PTT)[] argsQueue;
size_t maxNumStored;
}
static if (discarding == Discarding.fraction)
float fraction;
RT[Tuple!(PTT)] memo; // stores the result, indexed by arguments.
RT opCall(PTT args)
{
if (tuple(args) in memo) // Have we already seen these args?
{
return memo[tuple(args)]; // if yes, use the stored result
}
else // if not,
{
static if (storing == Storing.always)
{
RT result = fun(args);// compute the result and store it.
memo[tuple(args)] = result;
return result;
}
else // Storing.maximum
{
if (argsQueue.length >= maxNumStored)
{
static if (discarding == Discarding.oldest)
{
memo.remove(argsQueue[0]);
argsQueue = argsQueue[1..$];
}
else static if (discarding == Discarding.fraction)
{
auto num = to!size_t(argsQueue.length * fraction);
foreach(elem; argsQueue[0..num])
memo.remove(elem);
argsQueue = argsQueue[num..$];
}
else static if (discarding == Discarding.all)
{
memo = null;
argsQueue.length = 0;
}
}
RT result = fun(args);// compute the result and store it.
memo[tuple(args)] = result;
argsQueue ~= tuple(args);
return result;
}
}
}
}
\end{dcode}
And a few factory function to help creating those \DD{Memoize} structs:
\begin{dcode}
module memoize3;
import memoize2;
// No runtime arg -> always store
Memoize!(fun, Storing.always, Discarding.all)
memoize(alias fun)()
{
Memoize!(fun,
Storing.always,
Discarding.all) result;
return result;
}
// One runtime size_t arg -> maximum store / discarding all
Memoize!(fun, Storing.maximum, Discarding.all)
memoize(alias fun)(size_t max)
{
Memoize!(fun,
Storing.maximum,
Discarding.all) result;
result.maxNumStored = max;
return result;
}
// Two runtime args (size_t, double) -> maximum store / discarding a fraction
Memoize!(fun, Storing.maximum, Discarding.fraction)
memoize(alias fun)(size_t max, double fraction)
{
Memoize!(fun,
Storing.maximum,
Discarding.fraction) result;
result.maxNumStored = max;
result.fraction = fraction;
return result;
}
// One compile-time argument (discarding oldest), one runtime argument (max)
Memoize!(fun, Storing.maximum, discarding)
memoize(alias fun, Discarding discarding = Discarding.oldest)
(size_t max)
{
Memoize!(fun,
Storing.maximum,
Discarding.oldest) result;
result.maxNumStored = max;
return result;
}
\end{dcode}
Note that, due to the introduction of an \DD{opCall} operator, it's not possible to use a struct literal. We have to first create the struct, then initialize its fields.