1 /// General-purpose yet powerful functional programming oriented functions. 2 module tern.functional; 3 4 // TODO: Plane currently uses normal indexing, it should use Range(T) 5 // TODO: tap 6 public import std.functional: curry, compose, pipe, memoize, not, partial, reverseArgs, unaryFun, binaryFun, bind; 7 import tern.traits; 8 import tern.object : loadLength; 9 import std.conv; 10 import std.meta; 11 import std.typecons; 12 import std.parallelism; 13 import std.range : iota; 14 import std.array; 15 import std.string; 16 17 /// Tries to automagically instantiate `F` by argument. 18 package template Instantiate(alias F, ARGS...) 19 if (isDynamicLambda!F) 20 { 21 string args() 22 { 23 string setup() 24 { 25 string ret; 26 string p = F.stringof[(F.stringof.lastIndexOf("(") + 1)..$]; 27 string[] ps = p.split(", "); 28 foreach (i, _p; ps) 29 { 30 if (_p.replace("const ", "").replace("ref ", "").replace("shared ", "") 31 .replace("immutable ", "").replace("auto ", "").replace("scope ", "").split(" ").length > 1) 32 continue; 33 34 ret ~= i < ARGS.length ? "ARGS["~i.to!string~"], " : "ARGS[$-1], "; 35 } 36 return ret[0..(ret.length >= 2 ? $-2 : $)]; 37 } 38 39 alias D = mixin("F!("~setup~")"); 40 return setup.replace("ARGS[$-1]", fullIdentifier!(Unconst!(ReturnType!D))); 41 } 42 43 alias Instantiate = mixin("F!("~args~")"); 44 } 45 46 public: 47 /** 48 * Denatures `F` on `args` to create a void function with no arguments. 49 * 50 * This is particularly useful when (unsafely) invoking a function from another thread. 51 * 52 * Params: 53 * F = The function to denature. 54 * args = The arguments to denature `F` to. 55 * 56 * Returns: 57 * A `void function()` which invokes `F` on `args`. 58 * 59 * Remarks: 60 * Supports renaturing arguments by reference, which may be especially unsafe. 61 */ 62 auto denature(alias F, ARGS...)(auto ref ARGS args) @nogc 63 if (__traits(compiles, { F(args); })) 64 { 65 string makeFun() 66 { 67 string ret = "() { F("; 68 static foreach (i, ARG; ARGS) 69 ret ~= "*cast(ARGS["~i.to!string~"]*)a"~i.to!string~", "; 70 return ret[0..(ARGS.length == 0 ? $ : $-2)]~"); }"; 71 // TODO: blit back 72 } 73 74 static foreach (i, ARG; ARGS) 75 { 76 mixin("static shared(ARGS["~i.to!string~"])* a"~i.to!string~"; 77 a"~i.to!string~" = cast(shared(ARGS["~i.to!string~"])*)&args["~i.to!string~"];"); 78 } 79 void function() f = mixin(makeFun); 80 return f; 81 } 82 83 /** 84 * Renatures `F` to `SIG` on `args` to create a new arbitrary signature. 85 * 86 * Params: 87 * F = The function to renature. 88 * SIG = The new signature of `F` after renaturing. 89 * args = The arguments to renature `F` to. 90 * 91 * Returns: 92 * A `SIG` function which invokes `F` on `args`. 93 */ 94 public template renature(alias F, SIG...) 95 if (SIG.length >= 1) 96 { 97 auto renature(ARGS...)(auto ref ARGS args) @nogc 98 if (__traits(compiles, { F(args); })) 99 { 100 string makeParams() 101 { 102 string ret = "("; 103 static foreach (i, ARG; SIG[1..$]) 104 ret ~= fullIdentifier!ARG~", "; 105 return ret[0..(SIG.length == 1 ? $ : $-2)]~")"; 106 } 107 108 string makeFun() 109 { 110 string ret = makeParams~" { F("; 111 static foreach (i, ARG; ARGS) 112 ret ~= "cast(ARGS["~i.to!string~"]a"~i.to!string~", "; 113 return ret[0..(ARGS.length == 0 ? $ : $-2)]~"); return SIG[0].init; }"; 114 // TODO: blit back 115 } 116 117 static foreach (i, ARG; ARGS) 118 { 119 mixin("static shared ARGS["~i.to!string~"] a"~i.to!string~"; 120 a"~i.to!string~" = cast(shared(ARGS["~i.to!string~"]))args["~i.to!string~"];"); 121 } 122 mixin("SIG[0] function"~makeParams~" f = mixin(makeFun);"); 123 return f; 124 } 125 } 126 127 /* auto shrinkWrap(alias F, ARGS...)(auto ref ARGS args) 128 if (__traits(compiles, { F(args); })) 129 { 130 alias RETURN = typeof(F(args)); 131 string makeFun() 132 { 133 static if (is(RETURN == void)) 134 string ret = "() { F("; 135 else 136 string ret = "() { return F("; 137 static foreach (i, ARG; ARGS) 138 ret ~= "*a"~i.to!string~", "; 139 return ret[0..$-2]~"); }"; 140 } 141 142 static foreach (i, ARG; ARGS) 143 { 144 mixin("static ARGS["~i.to!string~"]* a"~i.to!string~"; 145 a"~i.to!string~" = &args["~i.to!string~"];"); 146 } 147 RETURN function() f = mixin(makeFun); 148 return f; 149 } */ 150 151 /** 152 * Asynchronously calls every function in `FUNCS` using the given arguments, and returns all of the values in a tuple. 153 * 154 * Params: 155 * F = The function to be called. 156 * args = The arguments to call `F` on. 157 * 158 * Returns: 159 * A tuple of all returned values from every function in `FUNCS`. 160 * 161 * Remarks: 162 * Race conditions are very likely, make sure that the functions are thread safe. 163 */ 164 public template juxt(FUNCS...) 165 { 166 /** 167 * Asynchronously calls every function in `FUNCS` using the given arguments, and returns all of the values in a tuple. 168 * 169 * Params: 170 * F = The function to be called. 171 * args = The arguments to call `F` on. 172 * 173 * Returns: 174 * A tuple of all returned values from every function in `FUNCS`. 175 * 176 * Remarks: 177 * Race conditions are very likely, make sure that the functions are thread safe. 178 */ 179 auto juxt(ARGS...)(auto ref ARGS args) 180 { 181 string makeTup() 182 { 183 string ret; 184 static foreach (F; FUNCS) 185 {{ 186 alias RETURN = typeof(F(args)); 187 ret ~= is(RETURN == void) ? "bool, " : fullIdentifier!RETURN~", "; 188 }} 189 return "Tuple!("~ret[0..$-2]~")"; 190 } 191 192 string makeCase() 193 { 194 string ret; 195 static foreach (j, F; FUNCS) 196 { 197 static if (is(typeof(F(args)) == void)) 198 { 199 ret ~= "case "~j.to!string~": 200 FUNCS["~j.to!string~"](args); 201 ret["~j.to!string~"] = true; 202 break;"; 203 } 204 else 205 { 206 ret ~= "case "~j.to!string~": 207 ret["~j.to!string~"] = FUNCS["~j.to!string~"](args); 208 break;"; 209 } 210 } 211 return ret; 212 } 213 214 mixin(makeTup) ret; 215 foreach (i; parallel(iota(0, FUNCS.length))) 216 { 217 switch (i) 218 { 219 mixin(makeCase); 220 default: assert(0); 221 } 222 } 223 return ret; 224 } 225 } 226 227 /** 228 * Dynamically tries to barter a range based lambda. 229 * 230 * Params: 231 * F = The lambda being fulfilled. 232 * index = The current index in the range, by ref. 233 * elem = The current element in the range, by ref. 234 * 235 * Remarks: 236 * - This will barter any kind of lambda, but throws if there are more than 3 arguments. 237 * - Has no explicit parameter checking, just tries to match a call. 238 * - Will allow for fulfilling normal functions, but has no optimizations and is simply for ease of use. 239 */ 240 auto barter(alias F, A, B)(auto ref A index, auto ref B elem) 241 if (isCallable!F) 242 { 243 static if (Arity!F == 3) 244 { 245 static if (isDynamicLambda!F) 246 { 247 alias R = Instantiate!(F, A, B); 248 static assert(!is(Parameters!R[$-1] == void), "Cannot have a folding lambda with a void return type!"); 249 static Parameters!R[$-1] prev; 250 } 251 else 252 { 253 static assert(!is(Parameters!R[$-1] == void), "Cannot have a folding lambda with a void return type!"); 254 static Parameters!R[$-1] prev; 255 } 256 } 257 258 static if (isDynamicLambda!F) 259 { 260 /* alias K = Instantiate!(F, A, B); 261 static if (!is(ReturnType!K == void)) 262 alias G = memoize!K; 263 else 264 alias G = K; */ 265 266 static if (Arity!F == 0) 267 return F!()(); 268 else static if (Arity!F == 1) 269 return Instantiate!(F, A, B)(index); 270 else static if (Arity!F == 2) 271 return Instantiate!(F, A, B)(index, elem); 272 else static if (Arity!F == 3) 273 { 274 auto ret = Instantiate!(F, A, B)(index, elem, prev); 275 static if (!is(typeof(ret) == bool)) 276 scope (exit) prev = ret; 277 return ret; 278 } 279 else 280 static assert(0, "Unable to barter dynamic lambda with argument count "~Arity!F.to!string); 281 } 282 else 283 { 284 static if (Arity!F == 0) 285 return F(); 286 else static if (Arity!F == 1) 287 return F(index); 288 else static if (Arity!F == 2) 289 return F(index, elem); 290 else static if (Arity!F == 3) 291 { 292 auto ret = F(index, elem, prev); 293 static if (!is(typeof(ret) == bool)) 294 scope (exit) prev = ret; 295 return ret; 296 } 297 else 298 static assert(0, "Unable to barter lambda with argument count "~Arity!F.to!string); 299 } 300 } 301 302 /// ditto 303 auto barter(alias F, A)(auto ref A elem) 304 if (isCallable!F) 305 { 306 static if (Arity!F == 2) 307 { 308 static if (isDynamicLambda!F) 309 { 310 alias R = Instantiate!(F, A); 311 static assert(!is(ReturnType!R == void), "Cannot have a folding lambda with a void return type!"); 312 static Parameters!R[$-1] prev; 313 } 314 else 315 { 316 static assert(!is(ReturnType!F == void), "Cannot have a folding lambda with a void return type!"); 317 static Parameters!R[$-1] prev; 318 } 319 } 320 321 static if (isDynamicLambda!F) 322 { 323 static if (Arity!F == 0) 324 return F!()(); 325 else static if (Arity!F == 1) 326 return Instantiate!(F, A)(elem); 327 else static if (Arity!F == 2) 328 { 329 auto ret = Instantiate!(F, A)(elem, prev); 330 scope (exit) prev = ret; 331 return ret; 332 } 333 else 334 static assert(0, "Unable to barter dynamic lambda with argument count "~Arity!F.to!string); 335 } 336 else 337 { 338 static if (Arity!F == 0) 339 return F(); 340 else static if (Arity!F == 1) 341 return F(elem); 342 else static if (Arity!F == 2) 343 { 344 auto ret = F(elem, prev.value); 345 scope (exit) prev = ret; 346 return ret; 347 } 348 else 349 static assert(0, "Unable to barter lambda with argument count "~Arity!F.to!string); 350 } 351 } 352 353 /// ditto 354 auto barter(alias F, A, B, _ = void)(A index, B elem) 355 if (isCallable!F) 356 { 357 static if (Arity!F == 3) 358 { 359 static if (isDynamicLambda!F) 360 { 361 alias R = Instantiate!(F, A, B); 362 static assert(!is(ReturnType!R == void), "Cannot have a folding lambda with a void return type!"); 363 static Unqual!(Parameters!R[$-1]) prev; 364 } 365 else 366 { 367 static assert(!is(ReturnType!F == void), "Cannot have a folding lambda with a void return type!"); 368 static Unqual!(Parameters!R[$-1]) prev; 369 } 370 } 371 372 static if (isDynamicLambda!F) 373 { 374 static if (Arity!F == 0) 375 return F!()(); 376 else static if (Arity!F == 1) 377 return Instantiate!(F, A, B)(index); 378 else static if (Arity!F == 2) 379 return Instantiate!(F, A, B)(index, elem); 380 else static if (Arity!F == 3) 381 { 382 auto ret = Instantiate!(F, A, B)(index, elem, prev); 383 static if (!is(typeof(ret) == bool)) 384 scope (exit) prev = ret; 385 return ret; 386 } 387 else 388 static assert(0, "Unable to barter dynamic lambda with argument count "~Arity!F.to!string); 389 } 390 else 391 { 392 static if (Arity!F == 0) 393 return F(); 394 else static if (Arity!F == 1) 395 return F(index); 396 else static if (Arity!F == 2) 397 return F(index, elem); 398 else static if (Arity!F == 3) 399 { 400 auto ret = F(index, elem, prev); 401 static if (!is(typeof(ret) == bool)) 402 scope (exit) prev = ret; 403 return ret; 404 } 405 else 406 static assert(0, "Unable to barter lambda with argument count "~Arity!F.to!string); 407 } 408 } 409 410 /** 411 * Iterates over every element in `range`, looking for matches. 412 * 413 * Calls `C` using bartering (see `tern.lambda`) when a match is made. 414 * 415 * Params: 416 * C = Callback function. 417 * range = The range being iterated over. 418 * elem = The element to match for. 419 */ 420 auto plane(alias C, A, B)(auto ref A range, scope B elem) 421 if (isCallable!C && isIndexable!A && isElement!(A, B)) 422 { 423 alias TYPE = ReturnType!(barter!(C, size_t, ElementType!A, void)); 424 enum RETURN = !is(TYPE == void); 425 static if (RETURN) 426 TYPE ret; 427 size_t i; 428 while (true) 429 { 430 if (range[i] != elem) 431 { 432 if (++i >= range.loadLength) 433 { 434 static if (RETURN) 435 return ret; 436 else 437 return; 438 } 439 continue; 440 } 441 442 static if (RETURN) 443 { 444 auto ret = barter!C(i, range[i]); 445 if (++i >= range.loadLength) 446 return ret; 447 } 448 else 449 { 450 barter!C(i, range[i]); 451 if (++i >= range.loadLength) 452 return; 453 } 454 } 455 } 456 457 /** 458 * Iterates over every element in `range`, looking for matches. 459 * 460 * Calls `C` using bartering (see `tern.lambda`) when a match is made. 461 * 462 * Params: 463 * C = Callback function. 464 * range = The range being iterated over. 465 * subrange = The subrange to match for. 466 */ 467 auto plane(alias C, A, B)(auto ref A range, scope B subrange) 468 if (isCallable!C && isIndexable!A && isIndexable!B && !isElement!(A, B)) 469 { 470 if (subrange.loadLength > range.loadLength) 471 return; 472 473 alias TYPE = ReturnType!(barter!(C, size_t, ElementType!A, void)); 474 enum RETURN = !is(TYPE == void); 475 static if (RETURN) 476 TYPE ret; 477 size_t i; 478 while (true) 479 { 480 auto slice = range[i..(i + subrange.loadLength)]; 481 if (slice != subrange) 482 { 483 if (++i + subrange.loadLength > range.loadLength) 484 { 485 static if (RETURN) 486 return ret; 487 else 488 return; 489 } 490 continue; 491 } 492 493 static if (RETURN) 494 { 495 ret = barter!C(i, slice); 496 if (++i + subrange.loadLength > range.loadLength) 497 return ret; 498 } 499 else 500 { 501 barter!C(i, slice); 502 if (++i + subrange.loadLength > range.loadLength) 503 return; 504 } 505 } 506 } 507 508 /** 509 * Iterates over every element in `range`, looking for matches. 510 * 511 * Calls `C` using bartering (see `tern.lambda`) when a match is made. 512 * 513 * Params: 514 * C = Callback function. 515 * F = The predicate to match for. 516 * range = The range being iterated over. 517 */ 518 auto plane(alias C, alias F, T)(auto ref T range) 519 if (isCallable!C && isIndexable!T && isCallable!F) 520 { 521 alias TYPE = ReturnType!(barter!(C, size_t, ElementType!T, void)); 522 enum RETURN = !is(TYPE == void); 523 static if (RETURN) 524 TYPE ret; 525 size_t i; 526 while (true) 527 { 528 if (!barter!F(i, range[i])) 529 { 530 if (++i >= range.loadLength) 531 { 532 static if (RETURN) 533 return ret; 534 else 535 return; 536 } 537 continue; 538 } 539 540 static if (RETURN) 541 { 542 ret = barter!C(i, range[i]); 543 if (++i >= range.loadLength) 544 return ret; 545 } 546 else 547 { 548 barter!C(i, range[i]); 549 if (++i >= range.loadLength) 550 return; 551 } 552 } 553 } 554 555 /** 556 * Iterates over every element in `range` in reverse, looking for matches. 557 * 558 * Calls `C` using bartering (see `tern.lambda`) when a match is made. 559 * 560 * Params: 561 * C = Callback function. 562 * range = The range being iterated over. 563 * elem = The element to match for. 564 */ 565 auto planeReverse(alias C, A, B)(auto ref A range, scope B elem) 566 if (isCallable!C && isIndexable!A && isElement!(A, B)) 567 { 568 alias TYPE = ReturnType!(barter!(C, size_t, ElementType!A, void)); 569 enum RETURN = !is(TYPE == void); 570 static if (RETURN) 571 TYPE ret; 572 size_t i = range.loadLength - 1; 573 while (true) 574 { 575 if (range[i] != elem) 576 { 577 if (--i < 0) 578 { 579 static if (RETURN) 580 return ret; 581 else 582 return; 583 } 584 continue; 585 } 586 587 static if (RETURN) 588 { 589 auto ret = barter!C(i, range[i]); 590 if (--i < 0) 591 return ret; 592 } 593 else 594 { 595 barter!C(i, range[i]); 596 if (--i < 0) 597 return; 598 } 599 } 600 } 601 602 /** 603 * Iterates over every element in `range` in reverse, looking for matches. 604 * 605 * Calls `C` using bartering (see `tern.lambda`) when a match is made. 606 * 607 * Params: 608 * C = Callback function. 609 * range = The range being iterated over. 610 * subrange = The subrange to match for. 611 */ 612 auto planeReverse(alias C, A, B)(auto ref A range, scope B subrange) 613 if (isCallable!C && isIndexable!A && isIndexable!B && !isElement!(A, B)) 614 { 615 if (subrange.loadLength > range.loadLength) 616 return; 617 618 alias TYPE = ReturnType!(barter!(C, size_t, ElementType!A, void)); 619 enum RETURN = !is(TYPE == void); 620 static if (RETURN) 621 TYPE ret; 622 size_t i = range.loadLength - subrange.loadLength; 623 while (true) 624 { 625 auto slice = range[i..(i + subrange.loadLength)]; 626 if (slice != subrange) 627 { 628 if (--i - subrange.loadLength < 0) 629 { 630 static if (RETURN) 631 return ret; 632 else 633 return; 634 } 635 continue; 636 } 637 638 static if (RETURN) 639 { 640 auto ret = barter!C(i, slice); 641 if (--i - subrange.loadLength < 0) 642 return ret; 643 } 644 else 645 { 646 barter!C(i, slice); 647 if (--i - subrange.loadLength < 0) 648 return; 649 } 650 } 651 } 652 653 /** 654 * Iterates over every element in `range` in reverse, looking for matches. 655 * 656 * Calls `C` using bartering (see `tern.lambda`) when a match is made. 657 * 658 * Params: 659 * C = Callback function. 660 * F = The predicate to match for. 661 * range = The range being iterated over. 662 */ 663 auto planeReverse(alias C, alias F, T)(auto ref T range) 664 if (isCallable!C && isIndexable!T && isCallable!F) 665 { 666 alias TYPE = ReturnType!(barter!(C, size_t, ElementType!T, void)); 667 enum RETURN = !is(TYPE == void); 668 static if (RETURN) 669 TYPE ret; 670 size_t i = range.loadLength; 671 while (true) 672 { 673 if (!barter!F(i, range[i])) 674 { 675 if (--i < 0) 676 { 677 static if (RETURN) 678 return ret; 679 else 680 return; 681 } 682 continue; 683 } 684 685 static if (RETURN) 686 { 687 auto ret = barter!C(i, range[i]); 688 if (--i < 0) 689 return ret; 690 } 691 else 692 { 693 barter!C(i, range[i]); 694 if (--i < 0) 695 return; 696 } 697 } 698 }