April 03, 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.
April 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.

Nice! I recall trying to use .length before but I've had some CT errors. It was probs my fault.

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

April 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
April 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:
>>    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));
April 03, 2014
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
April 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.
April 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 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.
April 03, 2014
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
1 2
Next ›   Last »