1 /** 2 * Functional tooling 3 * 4 * Authors: Tristan Brice Velloza Kildaire (deavmi) 5 */ 6 module niknaks.functional; 7 8 import std.traits : isAssignable, isFunction, isDelegate, ParameterTypeTuple, ReturnType; 9 import std.functional : toDelegate; 10 11 /** 12 * Predicate for testing an input type 13 * against a condition and returning either 14 * `true` or `false` 15 * 16 * Params: 17 * T = the input type 18 */ 19 template Predicate(T) 20 { 21 /** 22 * Parameterized delegate pointer 23 * taking in `T` and returning 24 * either `true` or `false` 25 */ 26 alias Predicate = bool delegate(T); 27 } 28 29 /** 30 * Given the symbol of a function or 31 * delegate this will return a new 32 * `Predicate` of it 33 * 34 * Params: 35 * func = the symbol of the function 36 * or delegate to make a predicate of 37 */ 38 template predicateOf(alias func) 39 if(isFunction!(func) || isDelegate!(func)) 40 { 41 static if(!__traits(isSame, ReturnType!(func), bool)) 42 { 43 pragma(msg, "Predicates are required to have a return type of bool"); 44 static assert(false); 45 } 46 47 // Obtain all paramaters 48 private alias params = ParameterTypeTuple!(func); 49 50 static if(params.length != 1) 51 { 52 pragma(msg, "Predicates are required to have an arity of 1"); 53 static assert(false); 54 } 55 56 // Obtain the predicate's input type 57 private alias predicateParameterType = params[0]; 58 59 // Created predicate delegate 60 private Predicate!(predicateParameterType) del; 61 62 /** 63 * Given the symbol of a function or 64 * delegate this will return a new 65 * `Predicate` of it 66 * 67 * Returns: the predicate 68 */ 69 Predicate!(predicateParameterType) predicateOf() 70 { 71 // If it is a function, first make it a delegate 72 static if(isFunction!(func)) 73 { 74 del = toDelegate(&func); 75 } 76 else 77 { 78 del = func; 79 } 80 81 return del; 82 } 83 } 84 85 version(unittest) 86 { 87 private bool isEven(int number) 88 { 89 return number%2==0; 90 } 91 } 92 93 /** 94 * Uses a `Predicate` which tests 95 * an integer input for evenness 96 * 97 * We create the predicate by 98 * passing in the symbol of the 99 * function or delegate we wish 100 * to use for testing truthiness 101 * to a template function 102 * `predicateOf!(alias)` 103 */ 104 unittest 105 { 106 Predicate!(int) pred = predicateOf!(isEven); 107 108 assert(pred(0) == true); 109 assert(pred(1) == false); 110 111 bool delegate(int) isEvenDel = toDelegate(&isEven); 112 pred = predicateOf!(isEvenDel); 113 114 assert(pred(0) == true); 115 assert(pred(1) == false); 116 } 117 118 /** 119 * Default exception which is thrown 120 * when `get()` is called on an 121 * `Optional!(T)` which has no 122 * value set 123 */ 124 public class OptionalException : Exception 125 { 126 /** 127 * Constructs a new `OptionalException` 128 * with the given message 129 * 130 * Params: 131 * msg = the error text 132 */ 133 this(string msg) 134 { 135 super(msg); 136 } 137 } 138 139 /** 140 * Optionals for a given type and 141 * with a customizable exception 142 * to be thrown when a value is 143 * not present and `get()` is 144 * called. 145 * 146 * Params: 147 * T = the value type 148 * onEmptyGet = the `Throwable` 149 * to be called when `get()` is 150 * called and no value is present 151 */ 152 template Optional(T, onEmptyGet = OptionalException, exceptionArgs...) 153 if(isAssignable!(Throwable, onEmptyGet) // && 154 // __traits(getVirtualMethods, onEmptyGet, "")[0] 155 ) // TODO: Check for this() with arity of 1 and string 156 { 157 /** 158 * The optional itself 159 */ 160 public struct Optional 161 { 162 /** 163 * The value 164 */ 165 private T value; 166 167 /** 168 * Flag for if value 169 * has been set or 170 * not 171 */ 172 private bool isSet = false; 173 174 /** 175 * Constructs an optional with 176 * the value already set 177 * 178 * Params: 179 * value = the value to set 180 */ 181 this(T value) 182 { 183 set(value); 184 } 185 186 /** 187 * Sets the optional's value 188 * 189 * Params: 190 * value = the value to set 191 */ 192 public void set(T value) 193 { 194 this.value = value; 195 this.isSet = true; 196 } 197 198 /** 199 * Checks if a value is present 200 * or not 201 * 202 * Returns: `true` if present, 203 * otherwise `false` 204 */ 205 public bool isPresent() 206 { 207 return isSet; 208 } 209 210 /** 211 * Checks if there is 212 * no value present 213 * 214 * Returns: `true` if 215 * no value is present, 216 * `false` otherwise 217 */ 218 public bool isEmpty() 219 { 220 return isPresent() == false; 221 } 222 223 /** 224 * Returns the value of this 225 * optional if it is set. If 226 * not set then an exception 227 * is thrown. 228 * 229 * Returns: the value 230 * Throws: 231 * Throwable if no value 232 * is present 233 */ 234 public T get() 235 { 236 if(!isPresent()) 237 { 238 static if(exceptionArgs.length) 239 { 240 throw new onEmptyGet(exceptionArgs); 241 } 242 else 243 { 244 throw new onEmptyGet("Optional has no value yet get was called"); 245 } 246 } 247 248 return value; 249 } 250 251 /** 252 * Creates an empty optional 253 * 254 * This is the same as doing `Optional!(T)()` 255 * or simply declaring a variable 256 * of the type `Optional!(T) 257 * 258 * Returns: an empty optional 259 */ 260 public static Optional!(T) empty() 261 { 262 return Optional!(T)(); 263 } 264 } 265 } 266 267 /** 268 * Creating an `Optional!(T)` with no 269 * value present and then trying to 270 * get the value, which results in 271 * an exception 272 */ 273 unittest 274 { 275 struct MyType 276 { 277 278 } 279 280 Optional!(MyType) d; 281 assert(d.isPresent() == false); 282 assert(d.isEmpty()); 283 284 d = Optional!(MyType)(); 285 assert(d.isPresent() == false); 286 assert(d.isEmpty()); 287 288 d = Optional!(MyType).empty(); 289 assert(d.isPresent() == false); 290 assert(d.isEmpty()); 291 292 try 293 { 294 d.get(); 295 assert(false); 296 } 297 catch(OptionalException) 298 { 299 assert(true); 300 } 301 } 302 303 /** 304 * Creating an `Optional!(T)` with a 305 * value present and then trying to 306 * get the value, which results in 307 * said value being returned 308 */ 309 unittest 310 { 311 Optional!(byte) f = Optional!(byte)(1); 312 assert(f.isPresent() == true); 313 assert(f.isEmpty() == false); 314 315 try 316 { 317 assert(1 == f.get()); 318 } 319 catch(OptionalException) 320 { 321 assert(false); 322 } 323 } 324 325 /** 326 * A result type 327 */ 328 @safe @nogc 329 public struct Result(Okay, Error) 330 { 331 private Okay okay_val; 332 private Error error_val; 333 334 private bool isSucc; 335 336 @disable 337 private this(); 338 339 private this(bool isSucc) 340 { 341 this.isSucc = isSucc; 342 } 343 344 /** 345 * Retuns the okay value 346 * 347 * Returns: the value 348 */ 349 public Okay ok() 350 { 351 return this.okay_val; 352 } 353 354 /** 355 * Returns the error value 356 * 357 * Returns: the value 358 */ 359 public Error error() 360 { 361 return this.error_val; 362 } 363 364 /** 365 * Returns the okayness of 366 * this result 367 * 368 * See_Also: `is_okay` 369 * Returns: a boolean 370 */ 371 public bool opCast(T)() 372 if(__traits(isSame, T, bool)) 373 { 374 return is_okay(); 375 } 376 377 /** 378 * Check if is okay 379 * 380 * Returns: `true` if 381 * okay, `false` otherwise 382 */ 383 public bool is_okay() 384 { 385 return this.isSucc == true; 386 } 387 388 /** 389 * Check if is erroneous 390 * 391 * Returns: `true` if 392 * erroneous, `false` 393 * otherwise 394 */ 395 public bool is_error() 396 { 397 return this.isSucc == false; 398 } 399 } 400 401 /** 402 * Constructs a new `Result` with the 403 * status set to okay and with the 404 * provided value. 405 * 406 * If you don't specify the type 407 * of the error value for this 408 * then it is assumed to be the 409 * same as the okay type. 410 * 411 * Params: 412 * okayVal = the okay value 413 * Returns: a `Result` 414 */ 415 @safe @nogc 416 public static Result!(OkayType, ErrorType) ok(OkayType, ErrorType = OkayType)(OkayType okayVal) 417 { 418 Result!(OkayType, ErrorType) result = Result!(OkayType, ErrorType)(true); 419 result.okay_val = okayVal; 420 421 return result; 422 } 423 424 /** 425 * Constructs a new `Result` with the 426 * status set to error and with the 427 * provided value. 428 * 429 * If you don't specify the type 430 * of the okay value for this 431 * then it is assumed to be the 432 * same as the error type. 433 * 434 * Params: 435 * errorVal = the error value 436 * Returns: a `Result` 437 */ 438 @safe @nogc 439 public static Result!(OkayType, ErrorType) error(ErrorType, OkayType = ErrorType)(ErrorType errorVal) 440 { 441 Result!(OkayType, ErrorType) result = Result!(OkayType, ErrorType)(false); 442 result.error_val = errorVal; 443 444 return result; 445 } 446 447 /** 448 * Tests the usage of okay 449 * result types 450 */ 451 unittest 452 { 453 auto a = ok("A successful result"); 454 assert(a.ok == "A successful result"); 455 assert(a.error == null); 456 457 // Should be Result!(string, string) 458 static assert(__traits(isSame, typeof(a.okay_val), string)); 459 static assert(__traits(isSame, typeof(a.error_val), string)); 460 461 // opCast to bool 462 assert(cast(bool)a); 463 464 // Validity checking 465 assert(a.is_okay()); 466 assert(!a.is_error()); 467 468 auto b = ok!(string, Exception)("A successful result"); 469 assert(b.ok == "A successful result"); 470 assert(b.error is null); 471 472 // Should be Result!(string, Exception) 473 static assert(__traits(isSame, typeof(b.okay_val), string)); 474 static assert(__traits(isSame, typeof(b.error_val), Exception)); 475 } 476 477 /** 478 * Tests the usage of error 479 * result types 480 */ 481 unittest 482 { 483 auto a = error(new Exception("A failed result")); 484 assert(a.ok is null); 485 assert(cast(Exception)a.error && (cast(Exception)a.error).msg == "A failed result"); 486 487 // Should be Result!(Exception, Exception) 488 static assert(__traits(isSame, typeof(a.okay_val), Exception)); 489 static assert(__traits(isSame, typeof(a.error_val), Exception)); 490 491 // opCast to bool 492 assert(!cast(bool)a); 493 494 // Validity checking 495 assert(!a.is_okay()); 496 assert(a.is_error()); 497 498 auto b = error!(Exception, string)(new Exception("A failed result")); 499 assert(a.ok is null); 500 assert(cast(Exception)a.error && (cast(Exception)a.error).msg == "A failed result"); 501 502 // Should be Result!(string, Exception) 503 static assert(__traits(isSame, typeof(b.okay_val), string)); 504 static assert(__traits(isSame, typeof(b.error_val), Exception)); 505 }