1 /++
2 Functions that manipulate other functions.
3 This module provides functions for compile time function composition. These
4 functions are helpful when constructing predicates for the algorithms in
5 $(MREF mir, ndslice).
6 $(BOOKTABLE $(H2 Functions),
7 $(TR $(TH Function Name) $(TH Description))
8     $(TR $(TD $(LREF naryFun))
9         $(TD Create a unary, binary or N-nary function from a string. Most often
10         used when defining algorithms on ranges and slices.
11     ))
12     $(TR $(TD $(LREF pipe))
13         $(TD Join a couple of functions into one that executes the original
14         functions one after the other, using one function's result for the next
15         function's argument.
16     ))
17     $(TR $(TD $(LREF not))
18         $(TD Creates a function that negates another.
19     ))
20     $(TR $(TD $(LREF reverseArgs))
21         $(TD Predicate that reverses the order of its arguments.
22     ))
23     $(TR $(TD $(LREF forward))
24         $(TD Forwards function arguments with saving ref-ness.
25     ))
26     $(TR $(TD $(LREF refTuple))
27         $(TD Removes $(LREF Ref) shell.
28     ))
29     $(TR $(TD $(LREF unref))
30         $(TD Creates a $(LREF RefTuple) structure.
31     ))
32     $(TR $(TD $(LREF __ref))
33         $(TD Creates a $(LREF Ref) structure.
34     ))
35 )
36 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
37 Authors: Ilya Yaroshenko, $(HTTP erdani.org, Andrei Alexandrescu (some original code from std.functional))
38 
39 Macros:
40 NDSLICE = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP)
41 +/
42 module mir.functional;
43 
44 private enum isRef(T) = is(T : Ref!T0, T0);
45 
46 import mir.math.common: optmath;
47 
48 public import core.lifetime : forward;
49 
50 @optmath:
51 
52 /++
53 Constructs static array.
54 +/
55 T[N] staticArray(T, size_t N)(T[N] a...)
56 {
57     return a;
58 }
59 
60 /++
61 Simple wrapper that holds a pointer.
62 It is used for as workaround to return multiple auto ref values.
63 +/
64 struct Ref(T)
65     if (!isRef!T)
66 {
67     @optmath:
68 
69     @disable this();
70     ///
71     this(ref T value) @trusted
72     {
73         __ptr = &value;
74     }
75     ///
76     T* __ptr;
77     ///
78     ref inout(T) __value() inout @property { return *__ptr; }
79     ///
80     alias __value this;
81     ///
82     bool opEquals(scope Ref!T rhs) const scope
83     {
84         return __value == rhs.__value;
85     }
86 
87     static if (__traits(hasMember, T, "toHash") || __traits(isScalar, T))
88     ///
89     size_t toHash() const
90     {
91         return hashOf(__value);
92     }
93 }
94 
95 /// Creates $(LREF Ref) wrapper.
96 Ref!T _ref(T)(ref T value)
97 {
98     return Ref!T(value);
99 }
100 
101 private mixin template _RefTupleMixin(T...)
102     if (T.length <= 26)
103 {
104     static if (T.length)
105     {
106         enum i = T.length - 1;
107         static if (isRef!(T[i]))
108             mixin(`@optmath @property ref ` ~ cast(char)('a' + i) ~ `() { return *expand[` ~ i.stringof ~ `].__ptr; }` );
109         else
110             mixin(`alias ` ~ cast(char)('a' + i) ~ ` = expand[` ~ i.stringof ~ `];`);
111         mixin ._RefTupleMixin!(T[0 .. $-1]);
112     }
113 }
114 
115 /++
116 Simplified tuple structure. Some fields may be type of $(LREF Ref).
117 Ref stores a pointer to a values.
118 +/
119 struct RefTuple(T...)
120 {
121     @optmath:
122     T expand;
123     alias expand this;
124     mixin _RefTupleMixin!T;
125 }
126 
127 /// Removes $(LREF Ref) shell.
128 alias Unref(V : Ref!T, T) = T;
129 /// ditto
130 template Unref(V : RefTuple!T, T...)
131 {
132     import std.meta: staticMap;
133     alias Unref = RefTuple!(staticMap!(.Unref, T));
134 }
135 
136 /// ditto
137 alias Unref(V) = V;
138 
139 /++
140 Returns: a $(LREF RefTuple) structure.
141 +/
142 RefTuple!Args refTuple(Args...)(auto ref Args args)
143 {
144     return RefTuple!Args(args);
145 }
146 
147 /// Removes $(LREF Ref) shell.
148 ref T unref(V : Ref!T, T)(scope return V value)
149 {
150     return *value.__ptr;
151 }
152 
153 /// ditto
154 Unref!(RefTuple!T) unref(V : RefTuple!T, T...)(V value)
155 {
156     typeof(return) ret;
157     foreach(i, ref elem; ret.expand)
158         elem = unref(value.expand[i]);
159     return ret;
160 }
161 
162 /// ditto
163 ref V unref(V)(scope return ref V value)
164 {
165     return value;
166 }
167 
168 /// ditto
169 V unref(V)(V value)
170 {
171     import std.traits: hasElaborateAssign;
172     static if (hasElaborateAssign!V)
173     {
174         import core.lifetime: move;
175         return move(value);
176     }
177     else
178         return value;
179 }
180 
181 private string joinStrings()(string[] strs)
182 {
183     if (strs.length)
184     {
185         auto ret = strs[0];
186         foreach(s; strs[1 .. $])
187             ret ~= s;
188         return ret;
189     }
190     return null;
191 }
192 
193 /++
194 Takes multiple functions and adjoins them together. The result is a
195 $(LREF RefTuple) with one element per passed-in function. Upon
196 invocation, the returned tuple is the adjoined results of all
197 functions.
198 Note: In the special case where only a single function is provided
199 (`F.length == 1`), adjoin simply aliases to the single passed function
200 (`F[0]`).
201 +/
202 template adjoin(fun...) if (fun.length && fun.length <= 26)
203 {
204     static if (fun.length != 1)
205     {
206         import std.meta: staticMap, Filter;
207         static if (Filter!(_needNary, fun).length == 0)
208         {
209             ///
210             @optmath auto adjoin(Args...)(auto ref Args args)
211             {
212                 template _adjoin(size_t i)
213                 {
214                     static if (__traits(compiles, &fun[i](forward!args)))
215                         enum _adjoin = "Ref!(typeof(fun[" ~ i.stringof ~ "](forward!args)))(fun[" ~ i.stringof ~ "](forward!args)), ";
216                     else
217                         enum _adjoin = "fun[" ~ i.stringof ~ "](forward!args), ";
218                 }
219 
220                 import mir.internal.utility;
221                 mixin("return refTuple(" ~ [staticMap!(_adjoin, Iota!(fun.length))].joinStrings ~ ");");
222             }
223         }
224         else alias adjoin = .adjoin!(staticMap!(naryFun, fun));
225     }
226     else alias adjoin = naryFun!(fun[0]);
227 }
228 
229 ///
230 @safe version(mir_core_test) unittest
231 {
232     static bool f1(int a) { return a != 0; }
233     static int f2(int a) { return a / 2; }
234     auto x = adjoin!(f1, f2)(5);
235     assert(is(typeof(x) == RefTuple!(bool, int)));
236     assert(x.a == true && x.b == 2);
237 }
238 
239 @safe version(mir_core_test) unittest
240 {
241     static bool F1(int a) { return a != 0; }
242     auto x1 = adjoin!(F1)(5);
243     static int F2(int a) { return a / 2; }
244     auto x2 = adjoin!(F1, F2)(5);
245     assert(is(typeof(x2) == RefTuple!(bool, int)));
246     assert(x2.a && x2.b == 2);
247     auto x3 = adjoin!(F1, F2, F2)(5);
248     assert(is(typeof(x3) == RefTuple!(bool, int, int)));
249     assert(x3.a && x3.b == 2 && x3.c == 2);
250 
251     bool F4(int a) { return a != x1; }
252     alias eff4 = adjoin!(F4);
253     static struct S
254     {
255         bool delegate(int) @safe store;
256         int fun() { return 42 + store(5); }
257     }
258     S s;
259     s.store = (int a) { return eff4(a); };
260     auto x4 = s.fun();
261     assert(x4 == 43);
262 }
263 
264 //@safe
265 version(mir_core_test) unittest
266 {
267     import std.meta: staticMap;
268     alias funs = staticMap!(naryFun, "a", "a * 2", "a * 3", "a * a", "-a");
269     alias afun = adjoin!funs;
270     int a = 5, b = 5;
271     assert(afun(a) == refTuple(Ref!int(a), 10, 15, 25, -5));
272     assert(afun(a) == refTuple(Ref!int(b), 10, 15, 25, -5));
273 
274     static class C{}
275     alias IC = immutable(C);
276     IC foo(){return typeof(return).init;}
277     RefTuple!(IC, IC, IC, IC) ret1 = adjoin!(foo, foo, foo, foo)();
278 
279     static struct S{int* p;}
280     alias IS = immutable(S);
281     IS bar(){return typeof(return).init;}
282     enum RefTuple!(IS, IS, IS, IS) ret2 = adjoin!(bar, bar, bar, bar)();
283 }
284 
285 private template needOpCallAlias(alias fun)
286 {
287     /* Determine whether or not naryFun need to alias to fun or
288      * fun.opCall. Basically, fun is a function object if fun(...) compiles. We
289      * want is(naryFun!fun) (resp., is(naryFun!fun)) to be true if fun is
290      * any function object. There are 4 possible cases:
291      *
292      *  1) fun is the type of a function object with static opCall;
293      *  2) fun is an instance of a function object with static opCall;
294      *  3) fun is the type of a function object with non-static opCall;
295      *  4) fun is an instance of a function object with non-static opCall.
296      *
297      * In case (1), is(naryFun!fun) should compile, but does not if naryFun
298      * aliases itself to fun, because typeof(fun) is an error when fun itself
299      * is a type. So it must be aliased to fun.opCall instead. All other cases
300      * should be aliased to fun directly.
301      */
302     static if (is(typeof(fun.opCall) == function))
303     {
304         import std.traits: Parameters;
305         enum needOpCallAlias = !is(typeof(fun)) && __traits(compiles, () {
306             return fun(Parameters!fun.init);
307         });
308     }
309     else
310         enum needOpCallAlias = false;
311 }
312 
313 private template _naryAliases(size_t n)
314     if (n <= 26)
315 {
316     static if (n == 0)
317         enum _naryAliases = "";
318     else
319     {
320         enum i = n - 1;
321         enum _naryAliases = _naryAliases!i ~ "alias " ~ cast(char)('a' + i) ~ " = args[" ~ i.stringof ~ "];\n";
322     }
323 }
324 
325 /++
326 Aliases itself to a set of functions.
327 
328 Transforms strings representing an expression into a binary function. The
329 strings must use symbol names `a`, `b`, ..., `z`  as the parameters.
330 If `functions[i]` is not a string, `naryFun` aliases itself away to `functions[i]`.
331 +/
332 template naryFun(functions...)
333     if (functions.length >= 1)
334 {
335     static foreach (fun; functions)
336     {
337         static if (is(typeof(fun) : string))
338         {
339             import mir.math.common;
340             /// Specialization for string lambdas
341             @optmath auto ref naryFun(Args...)(auto ref Args args)
342                 if (args.length <= 26)
343             {
344                 mixin(_naryAliases!(Args.length));
345                 return mixin(fun);
346             }
347         }
348         else static if (needOpCallAlias!fun)
349             alias naryFun = fun.opCall;
350         else
351             alias naryFun = fun;
352     }
353 }
354 
355 ///
356 @safe version(mir_core_test) unittest
357 {
358     // Strings are compiled into functions:
359     alias isEven = naryFun!("(a & 1) == 0");
360     assert(isEven(2) && !isEven(1));
361 }
362 
363 ///
364 @safe version(mir_core_test) unittest
365 {
366     alias less = naryFun!("a < b");
367     assert(less(1, 2) && !less(2, 1));
368     alias greater = naryFun!("a > b");
369     assert(!greater("1", "2") && greater("2", "1"));
370 }
371 
372 /// `naryFun` accepts up to 26 arguments.
373 @safe version(mir_core_test) unittest
374 {
375     assert(naryFun!("a * b + c")(2, 3, 4) == 10);
376 }
377 
378 /// `naryFun` can return by reference.
379 version(mir_core_test) unittest
380 {
381     int a;
382     assert(&naryFun!("a")(a) == &a);
383 }
384 
385 /// `args` parameter tuple
386 version(mir_core_test) unittest
387 {
388     assert(naryFun!("args[0] + args[1]")(2, 3) == 5);
389 }
390 
391 /// Multiple functions
392 @safe pure nothrow @nogc
393 version(mir_core_test) unittest
394 {
395     alias fun = naryFun!(
396         (uint a) => a,
397         (ulong a) => a * 2,
398         a => a * 3,
399     );
400 
401     int a = 10;
402     long b = 10;
403     float c = 10;
404 
405     assert(fun(a) == 10);
406     assert(fun(b) == 20);
407     assert(fun(c) == 30);
408 }
409 
410 @safe version(mir_core_test) unittest
411 {
412     static int f1(int a) { return a + 1; }
413     static assert(is(typeof(naryFun!(f1)(1)) == int));
414     assert(naryFun!(f1)(41) == 42);
415     int f2(int a) { return a + 1; }
416     static assert(is(typeof(naryFun!(f2)(1)) == int));
417     assert(naryFun!(f2)(41) == 42);
418     assert(naryFun!("a + 1")(41) == 42);
419 
420     int num = 41;
421     assert(naryFun!"a + 1"(num) == 42);
422 
423     // Issue 9906
424     struct Seen
425     {
426         static bool opCall(int n) { return true; }
427     }
428     static assert(needOpCallAlias!Seen);
429     static assert(is(typeof(naryFun!Seen(1))));
430     assert(naryFun!Seen(1));
431 
432     Seen s;
433     static assert(!needOpCallAlias!s);
434     static assert(is(typeof(naryFun!s(1))));
435     assert(naryFun!s(1));
436 
437     struct FuncObj
438     {
439         bool opCall(int n) { return true; }
440     }
441     FuncObj fo;
442     static assert(!needOpCallAlias!fo);
443     static assert(is(typeof(naryFun!fo)));
444     assert(naryFun!fo(1));
445 
446     // Function object with non-static opCall can only be called with an
447     // instance, not with merely the type.
448     static assert(!is(typeof(naryFun!FuncObj)));
449 }
450 
451 @safe version(mir_core_test) unittest
452 {
453     static int f1(int a, string b) { return a + 1; }
454     static assert(is(typeof(naryFun!(f1)(1, "2")) == int));
455     assert(naryFun!(f1)(41, "a") == 42);
456     string f2(int a, string b) { return b ~ "2"; }
457     static assert(is(typeof(naryFun!(f2)(1, "1")) == string));
458     assert(naryFun!(f2)(1, "4") == "42");
459     assert(naryFun!("a + b")(41, 1) == 42);
460     //@@BUG
461     //assert(naryFun!("return a + b;")(41, 1) == 42);
462 
463     // Issue 9906
464     struct Seen
465     {
466         static bool opCall(int x, int y) { return true; }
467     }
468     static assert(is(typeof(naryFun!Seen)));
469     assert(naryFun!Seen(1,1));
470 
471     struct FuncObj
472     {
473         bool opCall(int x, int y) { return true; }
474     }
475     FuncObj fo;
476     static assert(!needOpCallAlias!fo);
477     static assert(is(typeof(naryFun!fo)));
478     assert(naryFun!fo(1,1));
479 
480     // Function object with non-static opCall can only be called with an
481     // instance, not with merely the type.
482     static assert(!is(typeof(naryFun!FuncObj)));
483 }
484 
485 
486 /++
487 N-ary predicate that reverses the order of arguments, e.g., given
488 `pred(a, b, c)`, returns `pred(c, b, a)`.
489 +/
490 template reverseArgs(alias fun)
491 {
492     import std.meta: Reverse;
493     ///
494     @optmath auto ref reverseArgs(Args...)(auto ref Args args)
495         if (is(typeof(fun(Reverse!args))))
496     {
497         return fun(Reverse!args);
498     }
499 
500 }
501 
502 ///
503 @safe version(mir_core_test) unittest
504 {
505     int abc(int a, int b, int c) { return a * b + c; }
506     alias cba = reverseArgs!abc;
507     assert(abc(91, 17, 32) == cba(32, 17, 91));
508 }
509 
510 @safe version(mir_core_test) unittest
511 {
512     int a(int a) { return a * 2; }
513     alias _a = reverseArgs!a;
514     assert(a(2) == _a(2));
515 }
516 
517 @safe version(mir_core_test) unittest
518 {
519     int b() { return 4; }
520     alias _b = reverseArgs!b;
521     assert(b() == _b());
522 }
523 
524 @safe version(mir_core_test) unittest
525 {
526     alias gt = reverseArgs!(naryFun!("a < b"));
527     assert(gt(2, 1) && !gt(1, 1));
528     int x = 42;
529     bool xyz(int a, int b) { return a * x < b / x; }
530     auto foo = &xyz;
531     foo(4, 5);
532     alias zyx = reverseArgs!(foo);
533     assert(zyx(5, 4) == foo(4, 5));
534 }
535 
536 /++
537 Negates predicate `pred`.
538 +/
539 template not(alias pred)
540 {
541     static if (!is(typeof(pred) : string) && !needOpCallAlias!pred)
542     ///
543     @optmath bool not(T...)(auto ref T args)
544     {
545         return !pred(args);
546     }
547     else
548         alias not = .not!(naryFun!pred);
549 }
550 
551 ///
552 @safe version(mir_core_test) unittest
553 {
554     import std.algorithm.searching : find;
555     import std.uni : isWhite;
556     string a = "   Hello, world!";
557     assert(find!(not!isWhite)(a) == "Hello, world!");
558 }
559 
560 @safe version(mir_core_test) unittest
561 {
562     assert(not!"a != 5"(5));
563     assert(not!"a != b"(5, 5));
564 
565     assert(not!(() => false)());
566     assert(not!(a => a != 5)(5));
567     assert(not!((a, b) => a != b)(5, 5));
568     assert(not!((a, b, c) => a * b * c != 125 )(5, 5, 5));
569 }
570 
571 private template _pipe(size_t n)
572 {
573     static if (n)
574     {
575         enum i = n - 1;
576         enum _pipe = "f[" ~ i.stringof ~ "](" ~ ._pipe!i ~ ")";
577     }
578     else
579         enum _pipe = "args";
580 }
581 
582 private template _unpipe(alias fun)
583 {
584     import std.traits: TemplateArgsOf, TemplateOf;
585     static if (__traits(compiles, TemplateOf!fun))
586         static if (__traits(isSame, TemplateOf!fun, .pipe))
587             alias _unpipe = TemplateArgsOf!fun;
588         else
589             alias _unpipe = fun;
590     else
591         alias _unpipe = fun;
592 
593 }
594 
595 private enum _needNary(alias fun) = is(typeof(fun) : string) || needOpCallAlias!fun;
596 
597 /++
598 Composes passed-in functions `fun[0], fun[1], ...` returning a
599 function `f(x)` that in turn returns
600 `...(fun[1](fun[0](x)))...`. Each function can be a regular
601 functions, a delegate, a lambda, or a string.
602 +/
603 template pipe(fun...)
604 {
605     static if (fun.length != 1)
606     {
607         import std.meta: staticMap, Filter;
608         alias f = staticMap!(_unpipe, fun);
609         static if (f.length == fun.length && Filter!(_needNary, f).length == 0)
610         {
611             ///
612             @optmath auto ref pipe(Args...)(auto ref Args args)
613             {
614                 return mixin (_pipe!(fun.length));
615             }
616         }
617         else alias pipe = .pipe!(staticMap!(naryFun, f));
618     }
619     else alias pipe = naryFun!(fun[0]);
620 }
621 
622 ///
623 @safe version(mir_core_test) unittest
624 {
625     assert(pipe!("a + b", a => a * 10)(2, 3) == 50);
626 }
627 
628 /// `pipe` can return by reference.
629 version(mir_core_test) unittest
630 {
631     int a;
632     assert(&pipe!("a", "a")(a) == &a);
633 }
634 
635 /// Template bloat reduction
636 version(mir_core_test) unittest
637 {
638     enum  a = "a * 2";
639     alias b = e => e + 2;
640 
641     alias p0 = pipe!(pipe!(a, b), pipe!(b, a));
642     alias p1 = pipe!(a, b, b, a);
643 
644     static assert(__traits(isSame, p0, p1));
645 }
646 
647 @safe version(mir_core_test) unittest
648 {
649     import std.algorithm.comparison : equal;
650     import std.algorithm.iteration : map;
651     import std.array : split;
652     import std.conv : to;
653 
654     // First split a string in whitespace-separated tokens and then
655     // convert each token into an integer
656     assert(pipe!(split, map!(to!(int)))("1 2 3").equal([1, 2, 3]));
657 }
658 
659 
660 struct AliasCall(T, string methodName, TemplateArgs...)
661 {
662     T __this;
663     alias __this this;
664 
665     ///
666     auto lightConst()() const @property
667     {
668         import mir.qualifier;
669         return AliasCall!(LightConstOf!T, methodName, TemplateArgs)(__this.lightConst);
670     }
671 
672     ///
673     auto lightImmutable()() immutable @property
674     {
675         import mir.qualifier;
676         return AliasCall!(LightImmutableOf!T, methodName, TemplateArgs)(__this.lightImmutable);
677     }
678 
679     this()(auto ref T value)
680     {
681         __this = value;
682     }
683     auto ref opCall(Args...)(auto ref Args args)
684     {
685         import std.traits: TemplateArgsOf;
686         mixin("return __this." ~ methodName ~ (TemplateArgs.length ? "!TemplateArgs" : "") ~ "(forward!args);");
687     }
688 }
689 
690 /++
691 Replaces call operator (`opCall`) for the value using its method.
692 The funciton is designed to use with  $(NDSLICE, topology, vmap) or $(NDSLICE, topology, map).
693 Params:
694     methodName = name of the methods to use for opCall and opIndex
695     TemplateArgs = template arguments
696 +/
697 template aliasCall(string methodName, TemplateArgs...)
698 {
699     /++
700     Params:
701         value = the value to wrap
702     Returns:
703         wrapped value with implemented opCall and opIndex methods
704     +/
705     AliasCall!(T, methodName, TemplateArgs) aliasCall(T)(T value) @property
706     {
707         return typeof(return)(value);
708     }
709 
710     /// ditto
711     ref AliasCall!(T, methodName, TemplateArgs) aliasCall(T)(return ref T value) @property @trusted
712     {
713         return  *cast(typeof(return)*) &value;
714     }
715 }
716 
717 ///
718 @safe pure nothrow version(mir_core_test) unittest
719 {
720     static struct S
721     {
722         auto lightConst()() const @property { return S(); }
723 
724         auto fun(size_t ct_param = 1)(size_t rt_param) const
725         {
726             return rt_param + ct_param;
727         }
728     }
729 
730     S s;
731 
732     auto sfun = aliasCall!"fun"(s);
733     assert(sfun(3) == 4);
734 
735     auto sfun10 = aliasCall!("fun", 10)(s);   // uses fun!10
736     assert(sfun10(3) == 13);
737 }
738 
739 /++
740 +/
741 template recurseTemplatePipe(alias Template, size_t N, Args...)
742 {
743     static if (N == 0)
744         alias recurseTemplatePipe = Args;
745     else
746     {
747         alias recurseTemplatePipe = Template!(.recurseTemplatePipe!(Template, N - 1, Args));
748     }
749 }
750 
751 ///
752 @safe version(mir_core_test) unittest
753 {
754     // import mir.ndslice.topology: map;
755     alias map(alias fun) = a => a; // some template
756     static assert (__traits(isSame, recurseTemplatePipe!(map, 2, "a * 2"), map!(map!"a * 2")));
757 }
758 
759 /++
760 +/
761 template selfAndRecurseTemplatePipe(alias Template, size_t N, Args...)
762 {
763     static if (N == 0)
764         alias selfAndRecurseTemplatePipe = Args;
765     else
766     {
767         alias selfAndRecurseTemplatePipe = Template!(.selfAndRecurseTemplatePipe!(Template, N - 1, Args));
768     }
769 }
770 
771 ///
772 @safe version(mir_core_test) unittest
773 {
774     // import mir.ndslice.topology: map;
775     alias map(alias fun) = a => a; // some template
776     static assert (__traits(isSame, selfAndRecurseTemplatePipe!(map, 2, "a * 2"), map!(pipe!("a * 2", map!"a * 2"))));
777 }
778 
779 /++
780 +/
781 template selfTemplatePipe(alias Template, size_t N, Args...)
782 {
783     static if (N == 0)
784         alias selfTemplatePipe = Args;
785     else
786     {
787         alias selfTemplatePipe = Template!(.selfTemplatePipe!(Template, N - 1, Args));
788     }
789 }
790 
791 ///
792 @safe version(mir_core_test) unittest
793 {
794     // import mir.ndslice.topology: map;
795     alias map(alias fun) = a => a; // some template
796     static assert (__traits(isSame, selfTemplatePipe!(map, 2, "a * 2"), map!(pipe!("a * 2", map!"a * 2"))));
797 }