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 }