digitalmars.D - Another interesting hack: expand a static array into parameter
- Andrej Mitrovic (26/26) Apr 02 2014 -----
- Andrej Mitrovic (31/32) Apr 02 2014 Cleaned up version:
- Timon Gehr (3/8) Apr 02 2014 I guess this would be more useful as a ref-returning @property function?
- Andrej Mitrovic (2/3) Apr 02 2014 Good point.
- Andrej Mitrovic (15/18) Apr 02 2014 It can even be done inside the template:
- =?ISO-8859-1?Q?Simen_Kj=E6r=E5s?= (17/36) Apr 02 2014 In fact, it does not even need to have arg and idx explicitly passed,
- Andrej Mitrovic (3/5) Apr 03 2014 Nice! I recall trying to use .length before but I've had some CT
- Andrej Mitrovic (5/7) Apr 03 2014 Actually, I've just realized the inner Delay() becomes a nested
- Artur Skawina (8/21) Apr 02 2014 template expand(alias A, alias M=Delay) {
- w0rp (4/12) Apr 02 2014 I kind of started this whole topic off from IRC.
- Andrej Mitrovic (5/10) Apr 03 2014 You can always use string mixins for these hacks. But they are
- monarch_dodra (9/22) Apr 03 2014 I agree that you should avoid string mixins when you can,
- Artur Skawina (28/40) Apr 03 2014 Actually, they are *much easier* to debug than the recursive templates
- Andrej Mitrovic (41/44) Apr 03 2014 Personally I think we need a third mechanism. I would totally love to
- Dicebot (8/10) Apr 03 2014 It would have been a very complex and non-precedent feature, much
- Artur Skawina (26/67) Apr 03 2014 Yes. But that would make D a very different language, so introducing
- bearophile (13/28) Apr 02 2014 ...
- Andrej Mitrovic (7/8) Apr 03 2014 But what is the performance cost? In D it is a compile-time feature,
- bearophile (7/14) Apr 03 2014 In D the minimal one. In Python is not that important. And in
----- import std.stdio; import std.typetuple; auto Delay(alias arg, size_t idx)() { return arg[idx]; } template expand(alias args, size_t idx = 0) { alias Args = typeof(args); static if (is(Args : C[N], C, size_t N)) { static if (idx < (N - 1)) alias expand = TypeTuple!(Delay!(args, idx), expand!(args, idx + 1)); else alias expand = Delay!(args, N - 1); } } void foo(int a, int b, int c) { writefln("a: %s, b: %s, c: %s", a, b, c); } void main() { int[3] arr = [1, 2, 3]; foo(expand!arr); } -----
Apr 02 2014
On Wednesday, 2 April 2014 at 19:40:41 UTC, Andrej Mitrovic wrote:snipCleaned up version: ----- import std.stdio; import std.traits; import std.typetuple; auto Delay(alias arg, size_t idx)() { return arg[idx]; } template expand(alias array, size_t idx = 0) if (isStaticArray!(typeof(array))) { alias Array = typeof(array); static if (idx + 1 < Array.length) alias expand = TypeTuple!(Delay!(array, idx), expand!(array, idx + 1)); else alias expand = Delay!(array, idx); } void print(int a) { writefln("1: %s", a); } void print(int a, int b) { writefln("2: %s %s", a, b); } void print(int a, int b, int c) { writefln("3: %s %s %s", a, b, c); } void main() { int[1] arr1 = [1]; int[2] arr2 = [1, 2]; int[3] arr3 = [1, 2, 3]; print(expand!arr1); print(expand!arr2); print(expand!arr3); } -----
Apr 02 2014
On 04/02/2014 09:46 PM, Andrej Mitrovic wrote:On Wednesday, 2 April 2014 at 19:40:41 UTC, Andrej Mitrovic wrote:Nice.snipCleaned up version: ... auto Delay(alias arg, size_t idx)() { return arg[idx]; }I guess this would be more useful as a ref-returning property function?
Apr 02 2014
On 4/2/14, Timon Gehr <timon.gehr gmx.ch> wrote:I guess this would be more useful as a ref-returning property function?Good point.
Apr 02 2014
On 4/2/14, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:On 4/2/14, Timon Gehr <timon.gehr gmx.ch> wrote:It can even be done inside the template: ----- template expand(alias array, size_t idx = 0) if (isStaticArray!(typeof(array))) { static property ref Delay(alias arg, size_t idx)() { return arg[idx]; } alias Array = typeof(array); static if (idx + 1 < Array.length) alias expand = TypeTuple!(Delay!(array, idx), expand!(array, idx + 1)); else alias expand = Delay!(array, idx); } -----I guess this would be more useful as a ref-returning property function?Good point.
Apr 02 2014
On 2014-04-02 20:54, Andrej Mitrovic wrote:On 4/2/14, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:In fact, it does not even need to have arg and idx explicitly passed, and the alias for Array is redundant: template expand(alias array, size_t idx = 0) if (isStaticArray!(typeof(array))) { property ref Delay() { return array[idx]; } static if (idx + 1 < array.length) { alias expand = TypeTuple!(Delay, expand!(array, idx + 1)); } else { alias expand = Delay; } } -- SimenOn 4/2/14, Timon Gehr <timon.gehr gmx.ch> wrote:It can even be done inside the template: ----- template expand(alias array, size_t idx = 0) if (isStaticArray!(typeof(array))) { static property ref Delay(alias arg, size_t idx)() { return arg[idx]; } alias Array = typeof(array); static if (idx + 1 < Array.length) alias expand = TypeTuple!(Delay!(array, idx), expand!(array, idx + 1)); else alias expand = Delay!(array, idx); } -----I guess this would be more useful as a ref-returning property function?Good point.
Apr 02 2014
On 4/2/14, Simen Kjærås <simen.kjaras gmail.com> wrote:In fact, it does not even need to have arg and idx explicitly passed, and the alias for Array is redundant.Nice! I recall trying to use .length before but I've had some CT errors. It was probs my fault.
Apr 03 2014
On 4/2/14, Simen Kjærås <simen.kjaras gmail.com> wrote:In fact, it does not even need to have arg and idx explicitly passed, and the alias for Array is redundant.Actually, I've just realized the inner Delay() becomes a nested function with a hidden context pointer. If you mark the function as 'static' the code will fail to compile. I'm not sure whether this has any performance implications.
Apr 03 2014
On 04/02/14 21:46, Andrej Mitrovic wrote:auto Delay(alias arg, size_t idx)() { return arg[idx]; } template expand(alias array, size_t idx = 0) if (isStaticArray!(typeof(array))) { alias Array = typeof(array); static if (idx + 1 < Array.length) alias expand = TypeTuple!(Delay!(array, idx), expand!(array, idx + 1)); else alias expand = Delay!(array, idx); }template expand(alias A, alias M=Delay) { mixin(q{alias expand = TypeTuple!(} ~ iota(A.length).map!q{",M!(A,"[!a..$]~text(a)~")"}().join() ~ q{);}); } SCNR. artur
Apr 02 2014
On Wednesday, 2 April 2014 at 21:53:57 UTC, Artur Skawina wrote:template expand(alias A, alias M=Delay) { mixin(q{alias expand = TypeTuple!(} ~ iota(A.length).map!q{",M!(A,"[!a..$]~text(a)~")"}().join() ~ q{);}); } SCNR. arturI kind of started this whole topic off from IRC. ... What have I done!?
Apr 02 2014
On 4/2/14, Artur Skawina <art.08.09 gmail.com> wrote:template expand(alias A, alias M=Delay) { mixin(q{alias expand = TypeTuple!(} ~ iota(A.length).map!q{",M!(A,"[!a..$]~text(a)~")"}().join() ~ q{);}); }You can always use string mixins for these hacks. But they are terrible to debug, and likely slower to compile. I can barely make out what the above code does even though I deal with mixins and templates every day.
Apr 03 2014
On Thursday, 3 April 2014 at 08:59:44 UTC, Andrej Mitrovic wrote:On 4/2/14, Artur Skawina <art.08.09 gmail.com> wrote:I agree that you should avoid string mixins when you can, however, with enough tweaking, you can sometimes format it into something readable. I've been playing around a lot with format and range formatting, and I find the results to be "almost readable": //( M!(A,i), ... ) enum s = q{alias expand = TypeTuple!(%(M!(A,%s)%|,%));} .format(iota(A.length));template expand(alias A, alias M=Delay) { mixin(q{alias expand = TypeTuple!(} ~ iota(A.length).map!q{",M!(A,"[!a..$]~text(a)~")"}().join() ~ q{);}); }You can always use string mixins for these hacks. But they are terrible to debug, and likely slower to compile. I can barely make out what the above code does even though I deal with mixins and templates every day.
Apr 03 2014
On 04/03/14 10:56, Andrej Mitrovic wrote:On 4/2/14, Artur Skawina <art.08.09 gmail.com> wrote:Actually, they are *much easier* to debug than the recursive templates -- because you can always look at the generated code, something that is impossible when using the templates.template expand(alias A, alias M=Delay) { mixin(q{alias expand = TypeTuple!(} ~ iota(A.length).map!q{",M!(A,"[!a..$]~text(a)~")"}().join() ~ q{);}); }You can always use string mixins for these hacks. But they are terrible to debug, and likely slower to compile.I can barely make out what the above code does even though I deal with mixins and templates every day.Demonstrating exactly this was of course the point of my post. What's hard to read is not the mixin part, that's just one mixin with one trivial declaration. It's the iota based chain that obfuscates what's going on. This kind of code gets proposed here all the time, and the effect is that people new to D see these examples, get a completely wrong impression of the language, run away screaming and never look back. In this case there is no really good alternative as the recursive- templates are unreadable and bug-prone, a foreach-based solution would be a bit too verbose, and D doesn't have a static-foreach. Looking at the above code now, I realize that some of the obfuscation wasn't required; it was caused by my ignorance of D's std lib (which i never use, except for meta-programming). So, a saner version could look like: template expand(alias A, alias C="A[I]") { auto ref M(alias I)() property { return mixin(C); } mixin(q{alias expand = TypeTuple!(} ~ iota(A.length).map!q{"M!"~text(a)}().join(",") ~ q{);}); } That is actually not so bad; I was initially hoping for this approach to turn out much, much worse... :^) I guess, I'll need to look for another example of functional-style-gone-bad... artur
Apr 03 2014
On 4/3/14, Artur Skawina <art.08.09 gmail.com> wrote:Actually, they are *much easier* to debug than the recursive templates -- because you can always look at the generated code, something that is impossible when using the templates.Personally I think we need a third mechanism. I would totally love to be able to write CTFE-style template code. Here's a demonstration: ----- template FilterInts(Args...) { foreach (T; Args) { static if (is(T == int)) FilterInts ~= T; // populate a type tuple } } void main() { static assert(is(FilterInts!(int, float, int, string) == TypeTuple!(int, int))); } ----- Or another one: ----- template GetFirstArray(Args...) { foreach (T; Args) { static if (isArray!T) { GetFirstArray = T; break; } } } void main() { static assert(is(GetFirstArray!(int, int[], float, float[]) == int[])); } ----- This is so much better and easier to understand than recursive templates. Imperative-style type extraction/manipulation rather than writing hard-to-grok recursive templates. Thinking about it, I think the only reason why recursive templates are a popular approach is because of C++ heritage. But I really think we can do better.
Apr 03 2014
On Thursday, 3 April 2014 at 13:38:48 UTC, Andrej Mitrovic wrote:static if (is(T == int)) FilterInts ~= T; // populate a type tupleIt would have been a very complex and non-precedent feature, much more so than a simple static foreach. I personally did not have any problems / delays with understanding that CTFE / string mixin snippet, it is not that bad. It does suffer from same issue as recursive template implementation though - lot of symbol bloat. Which reminds me of my old nocodegen proposal.
Apr 03 2014
On 04/03/14 15:38, Andrej Mitrovic wrote:On 4/3/14, Artur Skawina <art.08.09 gmail.com> wrote:Yes. But that would make D a very different language, so introducing something like it isn't really practical right now... I've turned the pseudocode below into real working D code, just to see how close to your solutions I could get. (And before somebody suggests it - yes, it can easily be done via templates. The whole point of this exercise is to see how the code looks without them.)Actually, they are *much easier* to debug than the recursive templates -- because you can always look at the generated code, something that is impossible when using the templates.Personally I think we need a third mechanism. I would totally love to be able to write CTFE-style template code. Here's a demonstration:template FilterInts(Args...) { foreach (T; Args) { static if (is(T == int)) FilterInts ~= T; // populate a type tuple } } void main() { static assert(is(FilterInts!(int, float, int, string) == TypeTuple!(int, int))); }template FilterInts(Args...) { mixin (q{ alias FilterInts = TypeTuple!(} ~ { string r; foreach (I, T; Args) if (is(T==int)) r ~= ",Args["[!r..$] ~ I.stringof ~ "]"; return r; }() ~ ");"); } Well, ugh. One can hardly see what is going on in there. One solution would be to support tuple indexing by tuples -- this would allow a simple implementation, similar to the one below. [IOW: (int, double, string)[0,2] == (int, string) ]template GetFirstArray(Args...) { foreach (T; Args) { static if (isArray!T) { GetFirstArray = T; break; } } } void main() { static assert(is(GetFirstArray!(int, int[], float, float[]) == int[])); }alias GetFirstArray(Args...) = Args[{ foreach (I, T; Args) if (isArray!T) return I; assert(0); }()];This is so much better and easier to understand than recursive templates. Imperative-style type extraction/manipulation rather than writing hard-to-grok recursive templates. Thinking about it, I think the only reason why recursive templates are a popular approach is because of C++ heritage. But I really think we can do better.Yep. But static-foreach is a non-trivial language addition (eg it must be able to build arg-lists aka tuples). artur
Apr 03 2014
Andrej Mitrovic:void foo(int a, int b, int c) { writefln("a: %s, b: %s, c: %s", a, b, c); } void main() { int[3] arr = [1, 2, 3]; foo(expand!arr); } -----In Python2 and Haskell it's a built-in feature, as it should be:...def foo((x, y, z)): print x, y, z1 2 3foo([1, 2, 3])...def bar(x, y, z): print x, y, z1 2 3 Prelude> let foo [x, y, z] = show x ++ ", " ++ show y ++ ", " ++ show z Prelude> let a = [1, 2, 3] Prelude> foo a "1, 2, 3" Bye, bearophilea = [1, 2, 3] bar(*a)
Apr 02 2014
On 4/2/14, bearophile <bearophileHUGS lycos.com> wrote:In Python2 and Haskell it's a built-in feature.But what is the performance cost? In D it is a compile-time feature, everything can be inlined. Also, what this shows is how you can take advantage of *existing* features to implement something interesting. It shows power, rather than the ability to implement special cases in the compiler/interpreter.
Apr 03 2014
Andrej Mitrovic:But what is the performance cost?In D the minimal one. In Python is not that important. And in Haskell it's efficient.Also, what this shows is how you can take advantage of *existing* features to implement something interesting. It shows power, rather than the ability to implement special cases in the compiler/interpreter.It's better to have a built-in syntax that works similarly with both fixed-size arrays and tuples. Bye, bearophile
Apr 03 2014