1 module universal.core.product;
2 
3 import std.typecons;
4 import std.typetuple;
5 import std.conv;
6 import universal.core.apply;
7 import universal.meta;
8 
9 /*
10   This module extends std.typecons.Tuple functionality in various ways.
11 */
12 
13 
14 /*
15   std.functional.adjoin is reimplemented to support named fields
16 */
17 template adjoin(fields...)
18 {
19   alias fieldNames = Filter!(isString, fields);
20   alias funcs = Filter!(isParameterized, fields);
21 
22   template adjoin(A...)
23   {
24     import std.range : iota;
25 
26     alias Proj(uint i) = Universal!(typeof(funcs[i](A.init)));
27     alias Projs = staticMap!(Proj, aliasSeqOf!(funcs.length.iota));
28     alias T = Tuple!(DeclSpecs!(Projs, fieldNames));
29       
30     T adjoin(A args) 
31     {
32       auto proj(uint i)() { return apply!(funcs[i])(args); }
33       alias projs = staticMap!(proj, aliasSeqOf!(funcs.length.iota));
34 
35       return T(projs);
36     }
37   }
38 }
39 
40 ////////////////////////////////////////////////////////////////////////////////
41 
42 /*
43   Concatenate tuples, preserving field names
44 */
45 auto tconcat(Tuples...)(Tuples tuples) if(Tuples.length > 1)
46 {
47   return tconcat(
48     tuple!(
49       Tuples[0].fieldNames,
50       Tuples[1].fieldNames
51     )(tuples[0][], tuples[1][]),
52     tuples[2..$]
53   );
54 }
55 auto tconcat(Types...)(Tuple!Types baseCase)
56 {
57   return baseCase;
58 }
59 
60 /*
61   Apply a function (or a template function) to every element of the tuple to produce a new tuple.
62 */
63 template tlift(alias f)
64 {
65   template tlift(A) if(isTuple!A)
66   {
67     alias B(uint i) = typeof(f(A[i].init));
68 
69     static if(text(A.fieldNames) == "")
70       alias names = TypeTuple!();
71     else
72       alias names = A.fieldNames;
73 
74     auto tlift(A a)
75     {
76             import std.range : iota;
77 
78       B!i apply(uint i)(A a) { return f(a[i]); }
79 
80       return a.adjoin!(
81         staticMap!(apply, aliasSeqOf!(A.Types.length.iota)),
82         names
83       );
84     }
85   }
86 }
87 
88 /*
89   Sometimes tuples come up a lot; they can be useful for visually grouping a set of arguments (particularly in UFCS chains).
90   In practice, I find this symbol is the best balance of unambiguity and low visual noise. YMMV
91 */
92 alias t_ = tuple;
93 
94 @("EXAMPLES") unittest
95 {
96   /*
97     the generic universal product function, adjoin
98   */
99   auto t1 = 1.adjoin!(x => x*2, x => x*3);
100     assert(t1[0] == 2);
101     assert(t1[1] == 3);
102 
103   /*
104     adjoin supports named fields.
105   */
106   auto t2 = 2.adjoin!(
107     q{sq}, x => x^^2,
108     q{cu}, x => x^^3,
109   );
110     assert(t2.sq == 4 && t2.cu == 8);
111 
112   ////////////////////
113 
114   /*
115     This helper function attempts to strike a balance between being unambiguous and minimizing visual noise.
116   */
117   assert(t_(9,2)[0] == 9);
118   /*
119     It supports named fields.
120   */
121   assert(t_!(q{a}, q{b})(9,2).b == 2);
122   /*
123     Because of D's optional parenthesis for nullary functions, t_ is analogous to "unit" in many functional languages
124     This can be handy in generic programming; for example: as a leaf in a call tree, where the parent node is a nullary function.
125   */
126   bool kTrue() { return true; }
127     assert(kTrue(t_[]));
128 
129   /*
130     Tuples form a monoid over field types where mappend = tconcat and mempty = t_
131   */
132   auto t3 = t2.tconcat(
133     t_!(q{c},q{d})
134          (9,   2)
135   );
136     assert(t3[0..2] == t2[]);
137     assert(t3[2..$] == t_(9,2)[]);
138   /*
139     Field names are preserved
140   */
141     assert(t3.sq == 4);
142     assert(t3.cu == 8);
143     assert(t3.c  == 9);
144     assert(t3.d  == 2);
145 
146   /*
147     tlift maps a generic function over all tuple elements
148   */
149   auto t4 = t3.tlift!(_ => 100);
150     assert(t4.sq == 100);
151     assert(t4.cu == 100);
152     assert(t4.c  == 100);
153     assert(t4.d  == 100);
154 
155   ////////////////////
156 
157   /*
158     common ops and idioms
159   */
160 
161   /*
162     rename fields
163   */
164   auto t5 = t2[].t_!(q{abc}, q{def});
165     assert(t5.abc == t2.sq);
166     assert(t5.def == t2.cu);
167 
168   /*
169     nfunctor map over tuples
170   */
171   assert(
172     t_(1,2).adjoin!(
173       _=>_[0] - 1,
174       _=>_[1] - 2,
175     )
176     == t_(0,0)
177   );
178 
179   /*
180     convert homogenously typed tuple to array
181   */
182   assert( 
183     [ t_(1,2,3)[] ]
184     == [1,2,3]
185   );
186   /*
187     convert heterogenously typed tuple to array
188   */
189   import std.conv : to;
190   assert( 
191     [ t_(1, 2.0, "3").tlift!(to!int)[] ]
192     == [1,2,3]
193   );
194 
195   /*
196     convert tuple to struct
197   */
198   struct Vec3 { float x, y, z; }
199   assert(
200    Vec3(t_(0, 2, -9.8)[])
201     == Vec3(0, 2, -9.8)
202   );
203   /*
204     convert struct to tuple
205   */
206   assert(
207     Vec3(1, 4, 7).tupleof.t_
208     == t_(1,4,7)
209   );
210 }