Skip to content

Instantly share code, notes, and snippets.

@PetarKirov
Last active January 1, 2022 16:51
Show Gist options
  • Save PetarKirov/a808c94857de84858accfb094c19bf77 to your computer and use it in GitHub Desktop.
Save PetarKirov/a808c94857de84858accfb094c19bf77 to your computer and use it in GitHub Desktop.
UFCS-enabled algorithms on alias sequences and "template lambdas"
/++
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 ~ "`");
}