1 module universal.core.apply;
2 
3 import std.typecons;
4 
5 /*
6 	Transform symbols into a form usable within `universal`.
7 
8 	The reason that symbols need to be normalized is because D functions don't compose.
9 		example 1: `void f(){}`: there is no symbol `g` for which either `g(f())` or `f(g())` exists.
10 		example 2: `auto f(A a, B b)` : there is no symbol `g` for which `f(g())` exists.
11 
12 	To work around these inconsistencies, the following assumptions are made by the internals of `universal`:
13 		1) All functions return some value.
14 		2) Any `tuple(a,b,c)` is alpha-equivalent to its expansion `a,b,c` if it is passed as the first argument to a function.
15 
16 	Assumption (2) allows us to emulate multiple return values, solving example 2.
17 	Both assumptions together solve example 1. In fact, `f` itself satisfies `g`.
18 		
19 	To support these assumptions, a function `f` is normalized by being lifted through `apply` as a template argument.
20 	If `f` returns `void`, `apply!f` calls `f` and returns the empty `Tuple!()`, aka `Unit`.
21 	`apply!f` may transform its arguments before passing them to `f`, either by expanding the first argument into multiple arguments (if it is a tuple) or by packing multiple arguments into a tuple. Preference is given to passing the arguments through untouched.
22 
23 	As a coincidental convenience, `apply` can be useful for performing a function on a range in a UFCS chain, in which some range-level information is needed (like `$` does for `.length` in the context of `opIndex`).
24 */
25 template apply(alias f, string file = __FILE__, size_t line = __LINE__)
26 {
27   template apply(A...)
28   {
29 		static if(is(typeof(f(A.init)) == B, B))
30 			enum pass;
31 		else static if(is(typeof(f(A.init[0][], A.init[1..$])) == B, B))
32 			enum expand;
33 		else static if(is(typeof(f(A.init.tuple)) == B, B))
34 			enum enclose;
35 		else 
36 		{
37 			pragma(msg, typeof(f(A.init)));
38 
39 			alias B = void;
40 
41 			static assert(0,
42 				"couldn't apply "~__traits(identifier, f)~A.stringof
43 			);
44 		}
45 
46 		static if(is(B == void))
47 			alias C = Unit;
48 		else
49 			alias C = B;
50 
51 		C apply(A a)
52 		{
53 			auto applied()
54 			{
55 				static if(is(pass))
56 				{ return f(a); }
57 				else static if(is(expand))
58 				{ return f(a[0][], a[1..$]); }
59 				else static if(is(enclose))
60 				{ return f(tuple(a)); }
61 			}
62 
63 			static if(is(typeof(applied()) == void))
64 			{ applied; return unit; }
65 			else
66 			{ return applied; }
67 		}
68   }
69 }
70 
71 /*
72 	In various places in this library, its more useful to think of the empty tuple, void, and "no parameters" as being the same thing. All of those things are converted to Unit on their way into the system.
73 */
74 alias Unit = Tuple!();
75 auto unit() { return tuple; }
76 
77 /*
78 	Convenience function, equivalent to lifting the identity function through apply, but doesn't waste the extra time attempting various compilations.
79 */
80 auto identity(A)(A a) { return a; }
81 auto identity(A...)(A a) { return a.tuple; }
82 
83 /*
84 	Represents the normalized form of the given types.
85 */
86 alias Universal(A...) = typeof(A.init.identity);
87 alias Universal(_: void) = Universal!();
88 
89 @("EXAMPLES") unittest
90 {
91 	static int add(int a, int b) { return a + b; }
92 	assert(apply!add(tuple(1,2)) == apply!add(1,2));
93 
94 	static void f() {}
95 	assert(apply!f == unit);
96 
97 	assert( !__traits(compiles, f(f)));
98 	assert(__traits(compiles, apply!f(apply!f)));
99 }