www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Another interesting hack: expand a static array into parameter

reply "Andrej Mitrovic" <andrej.mitrovich gmail.com> writes:
-----
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
next sibling parent reply "Andrej Mitrovic" <andrej.mitrovich gmail.com> writes:
On Wednesday, 2 April 2014 at 19:40:41 UTC, Andrej Mitrovic wrote:
 snip

Cleaned 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
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 04/02/2014 09:46 PM, Andrej Mitrovic wrote:
 On Wednesday, 2 April 2014 at 19:40:41 UTC, Andrej Mitrovic wrote:
 snip


Nice.
 Cleaned 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
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
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
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 4/2/14, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:
 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.

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); } -----
Apr 02 2014
prev sibling next sibling parent =?ISO-8859-1?Q?Simen_Kj=E6r=E5s?= <simen.kjaras gmail.com> writes:
On 2014-04-02 20:54, Andrej Mitrovic wrote:
 On 4/2/14, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:
 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.

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); } -----

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; } } -- Simen
Apr 02 2014
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
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, z



 foo([1, 2, 3])



 def bar(x, y, z): print x, y, z



 a = [1, 2, 3]
 bar(*a)



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, bearophile
Apr 02 2014
prev sibling next sibling parent Artur Skawina <art.08.09 gmail.com> writes:
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
prev sibling next sibling parent "w0rp" <devw0rp gmail.com> writes:
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.

 artur

I kind of started this whole topic off from IRC. ... What have I done!?
Apr 02 2014
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
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
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
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
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
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
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
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
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
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
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
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:
    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.

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));
Apr 03 2014
prev sibling next sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 04/03/14 10:56, Andrej Mitrovic wrote:
 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.

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.
 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
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
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
prev sibling next sibling parent "Dicebot" <public dicebot.lv> writes:
On Thursday, 3 April 2014 at 13:38:48 UTC, Andrej Mitrovic wrote:
         static if (is(T == int))
             FilterInts ~= T;  // populate a type tuple

It 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
prev sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 04/03/14 15:38, Andrej Mitrovic wrote:
 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:

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.)
 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