1 /** 2 Create a function that encapsulates if/else-if/else logic 3 */ 4 module ddash.functional.cond; 5 6 /// 7 unittest { 8 alias lessThanZero = (a) => a < 0; 9 alias greaterOrEqualThan10 = (a) => a >= 10; 10 alias identity = (a) => a; 11 alias negate = (a) => -a; 12 13 alias abs = cond!( 14 a => a == 1, 42, 15 2, a => a * 2, 16 3, 999, 17 lessThanZero, negate, 18 greaterOrEqualThan10, identity, 19 a => a * 100, 20 ); 21 22 assert(abs(1) == 42); 23 assert(abs(2) == 4); 24 assert(abs(3) == 999); 25 assert(abs(4) == 400); 26 assert(abs(5) == 500); 27 assert(abs(-3) == 3); 28 assert(abs(11) == 11); 29 30 alias str = cond!( 31 a => a < 1, "1", 32 a => a < 5, "5", 33 a => a < 10, "10", 34 "moar" 35 ); 36 37 assert(str(0) == "1"); 38 assert(str(2) == "5"); 39 assert(str(7) == "10"); 40 assert(str(11) == "moar"); 41 } 42 43 import ddash.common; 44 45 /** 46 Takes pairs of predicates and transforms and uses the first transform that a predicate 47 return true for. 48 49 For example in the following call: 50 51 --- 52 cond!( 53 pred1, trsnaform1, 54 pred2, trsnaform2, 55 . 56 . 57 predN, trsnaformN, 58 default 59 )(value); 60 --- 61 62 `predN` can either be a unary function or an expression. `transformN` can be a unary, 63 or binary function, or an expression. The unary function cannot be a string since 64 narrow strings will be treated as values. `default` is treated as a transform. 65 66 All transforms must return a common type, or void. If there's a common return then a 67 default transform must be provided. If all the transforms return void then a default 68 is not needed. 69 70 Params: 71 statements = pairs of predicates and transforms followed by a default transform 72 value = the value to evaluate 73 74 Returns: 75 Whatever is returned by the result that wins 76 77 Benchmarks: 78 79 A sample `cond` if/else chain was used with a mixture of expressions and unary functions and 80 iterated over. A couple of hand written if/else chains were compared. The first used lambdas 81 to evaluate their conditions, the second just used inline code. 82 83 --- 84 Benchmarking cond against hand written if/elses 85 cond: 2 hnsecs 86 hand written 1: 0 hnsecs 87 hand written 2: 0 hnsecs 88 --- 89 */ 90 template cond(statements...) { 91 import std.traits: isExpressions, isCallable, arity, isNarrowString; 92 import std.functional: unaryFun; 93 import bolts.traits: isUnaryOver; 94 static template resolve(alias f) { 95 auto resolve(V...)(V values) { 96 static if (isUnaryOver!(f, V) && !isNarrowString!(typeof(f))){ 97 return f(values); 98 } else static if (isCallable!f && arity!f == 0) { 99 return f(); 100 } else static if (isExpressions!f) { 101 return f; 102 } else { 103 static assert( 104 0, 105 "Could not resolve " ~ f.stringof ~ " with values " ~ V.stringof 106 ); 107 } 108 } 109 } 110 auto cond(T)(T value) { 111 immutable cases = statements.length / 2; 112 static foreach(I; 0 .. cases) {{ 113 static if (isExpressions!(statements[I * 2])) { 114 immutable c = statements[I * 2] == value; 115 } else { 116 immutable c = statements[I * 2](value); 117 } 118 if (c) { 119 return resolve!(statements[I * 2 + 1])(value); 120 } 121 }} 122 // Return default case only if one was provided 123 static if (cases * 2 < statements.length) { 124 return resolve!(statements[$ - 1])(value); 125 } else { 126 static if (statements.length > 0) { 127 static assert( 128 is(typeof(return) == void), 129 "no default for " ~ typeof(return).stringof 130 ~ " return. if any transform returns non void a default transform must be provided" 131 ); 132 } 133 return; 134 } 135 } 136 } 137 138 unittest { 139 static assert( 140 is( 141 typeof( 142 cond!( 143 1, () {} 144 )(3) 145 ) 146 == void 147 ) 148 ); 149 150 // If transforms return, default must be provided 151 // TODO: Re-evaluate this. It'll be a compiler error if one o the matches 152 // does not cond anyway so maybe this can be relaxed so that if a user knows 153 // they are matching all conditions then it's ok. 154 static assert( 155 !__traits( 156 compiles, 157 { 158 cond!( 159 1, 2 160 )(3); 161 } 162 ) 163 ); 164 } 165 166 unittest { 167 static auto g() { return 10; } 168 assert(cond!(1, g, 4)(1) == 10); 169 }