See more at the announce forum.
What if we had a "must override" method: https://forum.dlang.org/post/pskvoctnusqkpmjxjvfn@forum.dlang.org which reminds people to fill it in for child classes, even if there is an implementation in the base class already? Could prompt them toward mixin templates with reflection-based implementations.
I did two case studies to remove .stringof and reduce some string concatenation.
I wrote most of this on the discord chatroom: https://discord.com/channels/242094594181955585/242122752436338688/1059196133580214362
But here's some code comparisons. This is from DCompute:
http://arsdnet.net/dcode/stringof.d
http://arsdnet.net/dcode/nostringof.d
1 module dcompute.driver.ocl.util; 2 3 import std.range; 4 import std.meta; 5 import std.traits; 6 7 //deal with arrays seperately, in part to avoid any 8 //narrow-string idiocy 9 @property auto memSize(R)(R r) 10 if (is(R : T[], T)) 11 { 12 static if (is(R : T[], T)) 13 return r.length * T.sizeof; 14 else 15 static assert(false); 16 } 17 18 @property auto memSize(R)(R r) 19 if(isInputRange!R && hasLength!R && !is(R : T[], T)) 20 { 21 return r.length * (ElementType!R).sizeof; 22 } 23 24 T[Args.length + 1] propertyList(T,Args...)(Args args) 25 { 26 T[Args.length + 1] props; 27 foreach(i, arg; args) 28 props[i] = *cast(T*)(&arg); 29 props[$-1] = cast(T)0; 30 return props; 31 } 32 33 struct ArrayAccesssor(alias ptr, alias len) {} 34 35 struct StringzAccessor(alias ptr) {} 36 37 struct ZeroTerminatedArrayAccessor(alias ptr) {} 38 39 struct ArrayAccesssor2D(alias ptr, alias lens, alias len) {} 40 41 // Returned by ArrayAccesssor2D 42 struct RangeOfArray(T) 43 { 44 T** ptr; 45 size_t* lengths; 46 size_t length; 47 size_t index; 48 49 bool empty() 50 { 51 return index == length; 52 } 53 54 @property T[] front() 55 { 56 return ptr[index][0 .. lengths[index]]; 57 } 58 59 T[] opIndex(size_t i) 60 { 61 return ptr[i][0 .. lengths[i]]; 62 } 63 void popFront() 64 { 65 ++index; 66 } 67 68 @property size_t opDollar() { return length; } 69 } 70 71 string generateGetInfo(Info,alias func,string args = "raw")() 72 { 73 import std.string; 74 return helper!(Info.tupleof).format(func.stringof,args); 75 } 76 77 // A substitute for fullyQualifiedName to speed up compile time 78 private template isModule(alias a) { 79 static if (is(a) || is(typeof(a)) || a.stringof.length < 7) { 80 enum isModule = false; 81 } else { 82 enum isModule = a.stringof[0..7] == "module "; 83 } 84 } 85 86 private template partiallyQualifiedName(alias a) { 87 static if (isModule!a) { 88 enum partiallyQualifiedName = ""; 89 } else { 90 static if (!isModule!(__traits(parent, a))) { 91 enum prefix = partiallyQualifiedName!(__traits(parent, a)) ~ "."; 92 } else { 93 enum prefix = ""; 94 } 95 enum partiallyQualifiedName = prefix ~ __traits(identifier, a); 96 } 97 } 98 99 private template helper(Fields...) 100 { 101 static if (Fields.length == 0) 102 enum helper = ""; 103 104 else static if (is(typeof(Fields[0]) : ArrayAccesssor!(ptr,len),alias ptr,alias len)) 105 { 106 enum helper = "@property " ~ typeof(*ptr).stringof ~ "[] " ~ Fields[0].stringof ~ "()\n" ~ 107 "{\n" ~ 108 " return " ~ ptr.stringof ~ "[0 .. " ~ len.stringof ~"];"~ 109 "}\n" ~ helper!(Fields[1 .. $]); 110 } 111 else static if (is(typeof(Fields[0]) : StringzAccessor!ptr,alias ptr)) 112 { 113 enum helper = "@property char[] " ~ Fields[0].stringof ~ "()\n" ~ 114 "{\n" ~ 115 " import std.typecons; char[] ret;" ~ 116 " size_t len;" ~ 117 " %1$s(%2$s," ~ __traits(getAttributes, ptr).stringof ~ "[0], 0, null, &len);" ~ 118 " ret.length = len;" ~ 119 " %1$s(%2$s," ~ __traits(getAttributes, ptr).stringof ~ "[0], memSize(ret), ret.ptr, null);" ~ 120 " return ret;" ~ 121 "}\n" ~ helper!(Fields[1 .. $]); 122 } 123 else static if (is(typeof(Fields[0]) : ArrayAccesssor2D!(ptr,lens,len) , alias ptr, alias lens, alias len)) 124 { 125 enum helper = "@property RangeOfArray!(" ~ typeof(**ptr).stringof ~ ") " ~ Fields[0].stringof ~ "()\n" ~ 126 "{\n" ~ 127 " import std.typecons; size_t length; size_t* lengths; " ~ typeof(ptr).stringof ~ " ptr;" ~ 128 " %1$s(%2$s," ~ __traits(getAttributes, len).stringof ~ "[0],length.sizeof, &length,null);" ~ 129 " lengths = (new size_t[length]).ptr; ptr = (new " ~ typeof(*ptr).stringof ~ "[length]).ptr;" ~ 130 " %1$s(%2$s," ~ __traits(getAttributes, lens).stringof ~ "[0],lengths.sizeof, lengths,null);" ~ 131 " if (lengths[length - 1] == 0) length--;" ~ 132 " foreach(i; 0 .. length) \n{" ~ 133 " ptr[i] = (new " ~ typeof(**ptr).stringof ~ "[lengths[i]]).ptr;" ~ 134 " }\n" ~ 135 " %1$s(%2$s," ~ __traits(getAttributes, ptr).stringof ~ "[0], ptr.sizeof, ptr, null);" ~ 136 " return typeof(return)(ptr,lengths,length,0);" ~ 137 "}\n" ~ helper!(Fields[1 .. $]); 138 } 139 else 140 { 141 static if (is(typeof(Fields[0]) == enum)) 142 { 143 enum helper = "@property " ~ partiallyQualifiedName!(typeof(Fields[0])) ~ " " ~ Fields[0].stringof ~ "()\n" ~ 144 "{\n" ~ 145 " import std.typecons; typeof(return) ret;" ~ 146 "%1$s(%2$s,"~ __traits(getAttributes, Fields[0]).stringof ~ "[0], ret.sizeof, &ret, null);" ~ 147 "return ret; " ~ 148 "}\n" ~ helper!(Fields[1 .. $]); 149 150 } 151 else 152 { 153 enum helper = "@property " ~ typeof(Fields[0]).stringof ~ " " ~ Fields[0].stringof ~ "()\n" ~ 154 "{\n" ~ 155 " import std.typecons; typeof(return) ret;" ~ 156 "%1$s(%2$s,"~ __traits(getAttributes, Fields[0]).stringof ~ "[0], ret.sizeof, &ret, null);" ~ 157 "return ret; " ~ 158 "}\n" ~ helper!(Fields[1 .. $]); 159 } 160 } 161 } | 1 module dcompute.driver.ocl.util; 2 3 import std.range; 4 import std.meta; 5 import std.traits; 6 7 //deal with arrays seperately, in part to avoid any 8 //narrow-string idiocy 9 @property auto memSize(R)(R r) 10 if (is(R : T[], T)) 11 { 12 static if (is(R : T[], T)) 13 return r.length * T.sizeof; 14 else 15 static assert(false); 16 } 17 18 @property auto memSize(R)(R r) 19 if(isInputRange!R && hasLength!R && !is(R : T[], T)) 20 { 21 return r.length * (ElementType!R).sizeof; 22 } 23 24 T[Args.length + 1] propertyList(T,Args...)(Args args) 25 { 26 T[Args.length + 1] props; 27 foreach(i, arg; args) 28 props[i] = *cast(T*)(&arg); 29 props[$-1] = cast(T)0; 30 return props; 31 } 32 33 struct ArrayAccesssor(alias ptr, alias len) {} 34 35 struct StringzAccessor(alias ptr) {} 36 37 struct ZeroTerminatedArrayAccessor(alias ptr) {} 38 39 struct ArrayAccesssor2D(alias ptr, alias lens, alias len) {} 40 41 // Returned by ArrayAccesssor2D 42 struct RangeOfArray(T) 43 { 44 T** ptr; 45 size_t* lengths; 46 size_t length; 47 size_t index; 48 49 bool empty() 50 { 51 return index == length; 52 } 53 54 @property T[] front() 55 { 56 return ptr[index][0 .. lengths[index]]; 57 } 58 59 T[] opIndex(size_t i) 60 { 61 return ptr[i][0 .. lengths[i]]; 62 } 63 void popFront() 64 { 65 ++index; 66 } 67 68 @property size_t opDollar() { return length; } 69 } 70 71 mixin template generateGetInfo(Info,alias func, args...) 72 { 73 static foreach(Field; Info.tupleof) { 74 mixin helper!(Field, Info, func, args); 75 } 76 } 77 78 mixin template helper(alias Field, Info, alias func, args...) 79 { 80 static if (is(typeof(Field) : ArrayAccesssor!(ptr,len),alias ptr,alias len)) 81 { 82 mixin(q{@property typeof(*ptr)[] } ~ __traits(identifier, Field) ~ q{() 83 { 84 return ptr[0 .. len]; 85 } 86 }); 87 } 88 else static if (is(typeof(Field) : StringzAccessor!ptr,alias ptr)) 89 { 90 mixin(q{@property char[] } ~ __traits(identifier, Field) ~ q{() 91 { 92 char[] ret; 93 size_t len; 94 func(args, __traits(getAttributes, ptr)[0], 0, null, &len); 95 ret.length = len; 96 func(args, __traits(getAttributes, ptr)[0], memSize(ret), ret.ptr, null); 97 return ret; 98 } 99 }); 100 } 101 else static if (is(typeof(Field) : ArrayAccesssor2D!(ptr,lens,len) , alias ptr, alias lens, alias len)) 102 { 103 mixin(q{@property RangeOfArray!(typeof(**ptr)) } ~ __traits(identifier, Field) ~ q{() 104 { 105 size_t length; size_t* lengths; typeof(ptr) ptr; 106 func(args, __traits(getAttributes, len)[0],length.sizeof, &length,null); 107 lengths = (new size_t[length]).ptr; ptr = (new typeof(*ptr)[length]).ptr; 108 func(args, __traits(getAttributes, lens)[0],lengths.sizeof, lengths,null); 109 if (lengths[length - 1] == 0) length--; 110 foreach(i; 0 .. length) 111 { 112 ptr[i] = (new typeof(**ptr)[lengths[i]]).ptr; 113 } 114 func(args, __traits(getAttributes, ptr)[0], ptr.sizeof, ptr, null); 115 return typeof(return)(ptr,lengths,length,0); 116 } 117 }); 118 } 119 else 120 { 121 static if (is(typeof(Field) == enum)) 122 { 123 mixin(q{@property typeof(Field) } ~ __traits(identifier, Field) ~ q{() 124 { 125 typeof(return) ret; 126 func(args, __traits(getAttributes, Field)[0], ret.sizeof, &ret, null); 127 return ret; 128 } 129 }); 130 } 131 else 132 { 133 mixin(q{@property typeof(Field) } ~ __traits(identifier, Field) ~ q{() 134 { 135 typeof(return) ret; 136 func(args, __traits(getAttributes, Field)[0], ret.sizeof, &ret, null); 137 return ret; 138 } 139 }); 140 } 141 } 142 } |
Before on the left, after on the right. Call sites changed to use mixin thing!(args) instead of mixin(thing(args));. Compile time in initial test cut by over 70%.
Separate discord chat (linked above) also had one about interface forwarding where the user had a bunch of ugly string interpolating code. Here's the self-contained after code:
1 interface ITest { 2 void foo() @(4) @nogc; 3 string bar(int a, int b) const; 4 string bar(string s) nothrow; 5 } 6 7 class Impl :ITest { 8 void foo() {import core.stdc.stdio; printf("foo\n");} 9 string bar(int a, int b) const { return "lol"; } 10 string bar(string s) { return "rofl"; } 11 } 12 13 class A : ITest { 14 this() { member = new Impl;} 15 16 ITest member; 17 18 mixin ForwardInterfaceTo!(ITest, member); 19 } 20 21 mixin template ForwardInterfaceTo(IFace, alias member) { 22 static foreach(memberName; __traits(allMembers, IFace)) 23 static foreach(idx, overload; __traits(getOverloads, IFace, memberName)) { 24 // the EXTREMELY RARE case where stringof works, though technically you're 25 // still better off using to!string(idx) or similar 26 mixin("mixin helper!(overload, member) " ~ memberName ~ idx.stringof ~ ";"); 27 // this is to merge the overload sets from across the helper things 28 // if you didn't care about overloads, no need for mixin strings up here, but alas 29 // and these need mixin conats because it is basically all name 30 mixin("alias " ~ memberName ~ " = " ~ memberName ~ idx.stringof ~ "." ~ memberName ~ ";"); 31 32 // pity we don't have Paul's `mixin foo this;` thing. that's not always the right thing to do but it would cover this beautifully. alas. 33 } 34 } 35 36 string constness(alias member)() { 37 string ret; 38 foreach(attr; __traits(getFunctionAttributes, member)) 39 if(attr == "const" || attr == "immutable") 40 ret = attr; 41 return ret; 42 } 43 44 mixin template helper(alias member, alias forwardTo) { 45 static if(is(typeof(member) ReturnType == return)) 46 static if(is(typeof(member) Params == __parameters)) 47 mixin(q{ 48 ReturnType 49 // you must concat the name... 50 } ~ __traits(identifier, member) ~ q{ 51 // and the constness 52 (Params params) } ~ constness!member ~ q{ 53 @(__traits(getAttributes, member)) 54 { 55 // but not much of anything else 56 return __traits(child, __traits(child, this, forwardTo), member)(params); 57 } 58 }); 59 } 60 61 void main() { 62 import std.stdio; 63 auto a = new A; 64 a.foo(); 65 writeln(a.bar("foo")); 66 writeln(a.bar(4, 3)); 67 pragma(msg, __traits(getAttributes, A.foo)); 68 }
This is a bit uglier to support const and overloads in the interface, and I don't have the "before" code to compare against, but the general idea in both studies is:
And yes, I know, I'm being lazy this week but that's all for now.
I wrote at some length in a chatroom about mixin and since I'm behind schedule I'm gonna be lazy and link it here.