Last active
January 1, 2022 16:51
-
-
Save PetarKirov/a808c94857de84858accfb094c19bf77 to your computer and use it in GitHub Desktop.
UFCS-enabled algorithms on alias sequences and "template lambdas"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/++ | |
module rxd.meta2; | |
# UFCS-enabled algorithms on alias sequences and "template lambdas" | |
Building blocks: | |
* `apply(fun, args...)` - applies `args` to `fun` where `fun` is either a | |
function or template, through speculative evaluation. | |
* `Π` - dependent (templated) Pi type, used for capturing compile-time | |
sequences as values, so they can be used as first class objects. | |
``` | |
((pi) { int[pi.get] arr; })(π!42); | |
``` | |
* `π` - convenience `enum`, used for passing sequences as function | |
parameters and return values. User code, would likely use the lower-case | |
`π` in most cases, as opposed to upper-case `Π` directly. | |
* Polymorphic lambda - an implicitly templated anonymous function that can | |
be called with different argument types. Example signature: | |
``` | |
alias lambda(InputArgs..., OutputArgs...) = | |
(Π!InputArgs args) => π!OutputArgs; | |
``` | |
* `l`, `elementEnvelope` - envelope construction utils. An 'envelope' is | |
a sequence encapsulation of the form: | |
``` | |
alias envelope(seq...) = | |
Π!(Π!(seq[0]), Π!(seq[1]), ..., Π!(seq[$ - 1])); | |
``` | |
The sole purpose of this encapsulation is to preserve compile-time | |
information. Compare and contrast: | |
``` | |
unittest | |
{ | |
((pi) { static assert(pi.get == 42); })(π!42); // OK | |
((x) { static assert(x == 42); })(42); // <- Error: variable x cannot | |
// be read at compile time. | |
} | |
``` | |
* Higher-order function (HOF) - a template like `staticMap(fun, args...)` | |
which uses `apply` internally to call `fun` with one or more arguments | |
from the `args` sequence. | |
* HOF UFCS adapter function - a UFCS-enabling function that wraps an | |
existing HOF or defines one internally. General example: | |
``` | |
auto xform(alias hof, alias fun, envelope)(envelope _) | |
if (is(envelope == Π!args, args...)) | |
{ | |
return π!(hof!(fun, args.get)); | |
} | |
``` | |
Authors: [Petar Kirov](https://github.com/ZombineDev) | |
+/ | |
module meta2; | |
import std.meta : Alias, AliasSeq; | |
version (unittest) | |
{ | |
import std.conv : to; | |
import std.format : format; | |
struct S | |
{ | |
int x; | |
double xy; | |
int xyz; | |
string wxyz; | |
S* vwxyz; | |
int[4] uvwxyz; | |
static void foo() {} | |
void bar() {} | |
} | |
pragma (msg, | |
l!(__traits(allMembers, S)) | |
.staticMap!(name => π!(name.get.length, name.get)) | |
.staticFilter!(t => t.get[0] % 2 == 0) | |
.staticMap!(t => t.get[1]).get, | |
"\n" | |
); | |
auto filterMembers(alias pred, type)(type _) | |
{ | |
alias MemberType(T, string name) = typeof(__traits(getMember, T, name)); | |
return l!(__traits(allMembers, type.get)) | |
.staticMap!(name => π!(MemberType!(type.get, name.get), name.get)) | |
.staticFilter!pred | |
.staticMap!(t => "%s %s".format(t.get[0].stringof, t.get[1])); | |
} | |
pragma (msg, | |
[ | |
π!(S) | |
.filterMembers!(t => | |
is(t.get[0] == T*, T) || | |
is(t.get[0] : T[], T)) | |
.get | |
].format!"struct Filtered\n{\n%-( %s;\n%);\n}" | |
); | |
auto memberFunctions(type)(type _) | |
{ | |
import std.traits : isSomeFunction; | |
alias Member(name...) = Alias!(__traits(getMember, type.get, name[0].get)); | |
return l!(__traits(allMembers, type.get)) | |
.staticMap!Member | |
.staticFilter!isSomeFunction | |
.staticMap!(t => | |
"%s %s".format( | |
__traits(identifier, t.get), | |
typeof(t.get).stringof)); | |
} | |
pragma (msg, [ π!S.memberFunctions.get ].to!string); | |
} | |
auto staticMap(alias fun, args)(args _) | |
{ | |
return π!(staticMapImpl!(fun, args.get)); | |
} | |
template staticMapImpl(alias fun, args...) | |
{ | |
static if (args.length == 0) | |
alias staticMapImpl = AliasSeq!(); | |
else static if (args.length == 1) | |
alias staticMapImpl = AliasSeq!(apply!(fun, π!(args[0]))); | |
else | |
alias staticMapImpl = | |
AliasSeq!( | |
staticMapImpl!(fun, args[ 0 .. $/2]), | |
staticMapImpl!(fun, args[$/2 .. $ ]) | |
); | |
} | |
auto staticFilter(alias pred, args)(args _) | |
{ | |
return π!(staticFilterImpl!(pred, args.get)); | |
} | |
template staticFilterImpl(alias pred, args...) | |
{ | |
static if (args.length == 0) | |
alias staticFilterImpl = AliasSeq!(); | |
else static if (args.length == 1) | |
static if (apply!(pred, args[0])) | |
alias staticFilterImpl = AliasSeq!(π!(args[0])); | |
else | |
alias staticFilterImpl = AliasSeq!(); | |
else | |
alias staticFilterImpl = | |
AliasSeq!( | |
staticFilterImpl!(pred, args[ 0 .. $/2]), | |
staticFilterImpl!(pred, args[$/2 .. $ ]) | |
); | |
} | |
/// Pi type and value definitions | |
template Π(envelope) | |
if (is(envelope == Π!args, args...)) | |
{ | |
alias Π = envelope; | |
} | |
/// ditto | |
template Π(args...) | |
if (args.length == 1 && is(typeof(args[0]) == Π!args2, args2...)) | |
{ | |
alias Π = args[0]; | |
} | |
/// ditto | |
template Π(args...) | |
if (args.length != 1 || (args.length == 1 && !is(typeof(args[0]) : Π!args2, args2...))) | |
{ | |
struct Π | |
{ | |
static if (args.length == 1) | |
static if (__traits(compiles, { auto x = args[0]; })) | |
enum get = args[0]; | |
else | |
alias get = args[0]; | |
else | |
alias get = args; | |
} | |
} | |
/// diito | |
enum π(args...) = Π!args.init; | |
/// | |
unittest | |
{ | |
static assert(is(Π!1 == Π!1)); | |
static assert(is(Π!(Π!1) == Π!1)); | |
static assert(is(Π!(Π!(Π!1)) == Π!1)); | |
static assert(is(Π!(Π!(Π!(Π!1))) == Π!1)); | |
static assert(is(typeof(π!1) == Π!1)); | |
static assert(is(typeof(π!(π!1)) == Π!1)); | |
static assert(is(typeof(π!(π!(π!1))) == Π!1)); | |
static assert(is(typeof(π!(π!(π!(π!1)))) == Π!1)); | |
alias l2 = Π!(Π!1, Π!2); | |
static assert(is(l2.get[0] == Π!1)); | |
static assert(is(l2.get[1] == Π!2)); | |
alias l23 = Π!(Π!(Π!1, Π!2, Π!3), Π!(Π!4, Π!5, Π!6)); | |
static assert(is(l23.get[0].get[0] == Π!1)); | |
static assert(is(l23.get[0].get[1] == Π!2)); | |
static assert(is(l23.get[0].get[2] == Π!3)); | |
static assert(is(l23.get[1].get[0] == Π!4)); | |
static assert(is(l23.get[1].get[1] == Π!5)); | |
static assert(is(l23.get[1].get[2] == Π!6)); | |
enum lv2 = π!(π!1, π!2); | |
static assert(is(typeof(lv2.get[0]) == Π!1)); | |
static assert(is(typeof(lv2.get[1]) == Π!2)); | |
enum lv23 = π!(π!(π!1, π!2, π!3), π!(π!4, π!5, π!6)); | |
static assert(is(typeof(lv23.get[0].get[0]) == Π!1)); | |
static assert(is(typeof(lv23.get[0].get[1]) == Π!2)); | |
static assert(is(typeof(lv23.get[0].get[2]) == Π!3)); | |
static assert(is(typeof(lv23.get[1].get[0]) == Π!4)); | |
static assert(is(typeof(lv23.get[1].get[1]) == Π!5)); | |
static assert(is(typeof(lv23.get[1].get[2]) == Π!6)); | |
} | |
/// Wraps each element of `args` in a `Π` envelope. | |
template elementEnvelope(args...) | |
{ | |
static if (args.length == 0) | |
alias elementEnvelope = AliasSeq!(); | |
else static if (args.length == 1) | |
alias elementEnvelope = AliasSeq!(π!(args[0])); | |
else | |
alias elementEnvelope = | |
AliasSeq!( | |
elementEnvelope!(args[ 0 .. $/2]), | |
elementEnvelope!(args[$/2 .. $ ]), | |
); | |
} | |
/// List envelope. | |
enum l(args...) = Π!(elementEnvelope!args).init; | |
/// | |
unittest | |
{ | |
enum list = l!(1, 2, 3); | |
static assert(is(typeof(list) == Π!(π!1, π!2, π!3))); | |
} | |
/// | |
template apply(alias func, args...) | |
{ | |
static if (__traits(compiles, { alias res = Alias!(func(args)); })) | |
// Evaluate `fun(args)` at compile-time and wrap the result | |
// in an `Alias`, so it can be used without special-casing | |
// in higher-order templates like `staticMap`. | |
alias apply = Alias!(func(args)); | |
else static if (is(typeof(func(args)))) | |
// `fun` is a regular callable, but we can't call it at compile-time. | |
// Delay the evaluation of `fun(args)` to run-time by wrapping it in a | |
// `@property` function. | |
@property auto ref apply() { return func(args); } | |
else static if (__traits(compiles, { alias res = Alias!(func!args); })) | |
// `fun` is a manifest constant (a.k.a. `enum`) template or a | |
// function template with no run-time parameters which can be evaulated | |
// at compile-time. Wrap the result in an `Alias`, so it can be aliased. | |
alias apply = Alias!(func!args); | |
else static if (__traits(compiles, { alias res = func!args; })) | |
// `fun` is template that yields an `AliasSeq`, or something that can't | |
// be wrapped with `Alias`. | |
alias apply = func!args; | |
else | |
alias apply = Alias!(func(args)); | |
//static assert(0, "Cannot call / instantiate `" ~ | |
// __traits(identifier, func) ~ "` with arguments: `" ~ | |
// args.stringof ~ "`"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
See also: