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 }