Skip to content

Instantly share code, notes, and snippets.

@pbackus
Last active June 17, 2020 16:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pbackus/0a70419eb8bece52f3a08edfe7b6019b to your computer and use it in GitHub Desktop.
Save pbackus/0a70419eb8bece52f3a08edfe7b6019b to your computer and use it in GitHub Desktop.
"Argument-Dependent Lookup" for D
module example;
import extension;
import lib;
// Can extend types from other modules
bool empty(A a) { return false; }
char front(A a) { return 'a'; }
void popFront(A a) {}
// ...but can't hijack existing methods
char front(C c) { return 'x'; }
void main()
{
import std.range: take, only;
import std.algorithm: equal;
A a;
assert(a.extended.take(3).equal(only('a', 'a', 'a')));
B b;
assert(b.extended.take(3).equal(only('b', 'b', 'b')));
C c;
assert(c.extended.take(3).equal(only('c', 'c', 'c')));
}
/// Extended methods with C++-esque argument-depended lookup
module extension;
/// The name of the module that defines sym
private template moduleName(alias sym)
{
alias parent(alias sym) = __traits(parent, sym);
enum bool isPackage(alias sym) = __traits(isPackage, sym);
string trimModuleName(alias sym)()
{
static if (isPackage!sym)
return sym.stringof["package ".length .. $];
else
return sym.stringof["module ".length .. $];
}
static if (__traits(compiles, parent!sym)) {
static if (__traits(isModule, sym) || isPackage!sym)
enum string moduleName = moduleName!(parent!sym) ~ "." ~ trimModuleName!sym;
else
enum string moduleName = moduleName!(parent!sym);
} else {
enum string moduleName = trimModuleName!sym;
}
}
/// Inline import
private template from(string module_)
{
mixin("import from = ", module_, ";");
}
/// Finds a method using argument-dependent lookup
template extendedMethod(string method, string context = __MODULE__)
{
auto ref extendedMethod(T, Args...)(auto ref T obj, auto ref Args args)
{
import core.lifetime: forward;
static if (__traits(compiles, mixin("obj.", method, "(forward!args)"))) {
// Normal method
return mixin("obj.", method, "(forward!args)");
} else static if (__traits(compiles,
__traits(getMember, from!(moduleName!T), method)(forward!(obj, args))
)) {
// UFCS method from defining module
return __traits(getMember, from!(moduleName!T), method)(forward!(obj, args));
} else static if (__traits(compiles,
__traits(getMember, from!context, method)(forward!(obj, args)),
)) {
// UFCS method from calling module
return __traits(getMember, from!context, method)(forward!(obj, args));
} else {
import std.traits: fullyQualifiedName;
static assert(false,
"no extended method `" ~ method ~ "` found for type `"
~ fullyQualifiedName!T ~ "` in module `" ~ context ~ "`"
);
}
}
}
/**
* A wrapper that extends the methods of `T` to include UFCS "methods" from
* `T`'s module as well as the module given by `context` (by default, that of
* the calling code).
*/
struct Extended(T, string context = __MODULE__)
{
import std.meta: staticIndexOf;
T obj;
alias obj this;
auto ref opDispatch(string method, Args...)(auto ref Args args)
{
import core.lifetime: forward;
return obj.extendedMethod!(method, context)(forward!args);
}
}
/// Factory function for `Extended`
Extended!(T, context) extended(T, string context = __MODULE__)(T obj)
{
return Extended!(T, context)(obj);
}
module lib;
struct A {}
struct B {}
bool empty(B b) { return false; }
char front(B b) { return 'b'; }
void popFront(B b) {}
struct C
{
bool empty() { return false; }
int front() { return 'c'; }
void popFront() {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment