April 24, 2020
On 4/24/2020 5:55 AM, Nick Treleaven wrote:
> On Friday, 24 April 2020 at 08:04:29 UTC, Walter Bright wrote:
>> On 4/24/2020 1:03 AM, Walter Bright wrote:
>>> On 4/24/2020 12:10 AM, Manu wrote:
>>>>    alias Tup = AliasSeq!(0, 2, 3);
>>>>    void fun(int);
>>>>    fun(Tup);  // scalar argument receives tuple, it can expand, so: fun(0), fun(1), fun(2)
>>>
>>> Write it as:
>>>
>>>      Tup.fun();
>>
>> Incidentally, belay that. That will currently produce: fun(0, 2, 3);
> 
> This syntax is an unfortunate inconsistency with your proposal, but how often is variadic UFCS used ATM? Its existence has been pointed out in a NG reply before (I think by Timon), but it seemed to surprise people. Perhaps it could be deprecated - use fun(Tup) instead. The latter is more intuitive as people tend to think UFCS is for the first argument, not multiple arguments.

Whether it's intuitive or not depends on your point of view. For example, if you view a tuple as members of a struct, the current behavior makes perfect sense. (I've actually wanted to make a tuple equivalent to a struct, but the darned function call ABI got in the way.)


>> Of course,
>>
>>     fun(1, Tup);
>>
>> cannot be rewritten in this way
> 
> AliasSeq!(1, Tup).fun(); // fun(1); fun(0); fun(2); fun(3);

Are you sure? It does fun(1, 0, 2, 3) when I try it.
April 24, 2020
On 4/24/2020 10:10 AM, Steven Schveighoffer wrote:
> The point of this is to reduce the amount of "trivial" templates. Every time a template is instantiated, it consumes memory in the compiler, it creates more stuff to manage in the symbol tables, and because templates have to "be the same" for every instantiation, you have weird paradoxes.

Surprisingly, I do get this!


> One of the fundamental problems with template power in D is that in order to operate on lists, you need a functional-style recurse or divide and conquer, which multiplies the number of templates needed by possibly hundreds or thousands. This takes time, resources, and can result in things that don't compile or are extremely slow to compile.
> 
> What I think this proposal does is to remove an entire class of recursive templates, and replaces them with simple loops that the compiler can execute blindfolded with 4 cores tied behind its back with ease, faster than the template expansions required by e.g. staticMap.

No argument there.


> I think the ellipsis version is superior simply because it has more expressive power (see my posts elsewhere). An ideal proposal would allow all things to be handled within one expression, but the ellipsis is better in that it is unambiguous and does not break code.

My issue is finding the best way to do this. Adding powerful syntax "just because we can" leads to an unusable language. Queue my opposition to AST macros.


> And actually, with a staticIota-like thing you could do every third tuple member quite easily.
> 
> alias everyThird = staticIota!(2, Tup.length, 3); // start, end, step
> 
> alias everyThirdMember = (Tup...[everyThird])...;

Touche. Well done.

Let's not fall into the mode of only looking at the way C++ did it and not seeing other ways. C++ has problems (like not having arrays) that lead it in different directions for solving array-like problems.

What do other languages do? How are things like this expressed in mathematics?


> staticIota is kind of another primitive that is super-useful, and would be easy for the compiler to provide.

Where does one stop in adding operators to the language?

Another approach to resolving the original problem (template instantiation bloat) is for the compiler to recognize templates like AliasSeq as "special" and implement them directly.


For example, AliasSeq is defined as:

  template AliasSeq(TList...) { alias AliasSeq = TList; }

This pattern is so simple it can be recognized by the compiler. (Having std.meta.AliasSeq being a special name known to the compiler is not necessary.)
The compiler already recognizes certain patterns, such as the expression that does a rotate, and then generates the CPU rotate instruction for it.
April 24, 2020
On 4/24/20 4:15 PM, Walter Bright wrote:

> 
> Since then it's an array, use the existing array folding methods.

This is probably good enough, because we can generate arrays at compile-time and process them via CTFE.

Some key targets within std.meta are anySatisfy/allSatisfy.

A quick stab at this (I'm going to stick with the ellipsis version as it's easy to ):

import std.algorithm : canFind;
enum anySatisfy(alias F, T...) = [F!(T)...].canFind(true);
enum allSatisfy(alias F, T...) = ![F!(T)...].canFind(false);

Wow, that reads so clean.

I'm so in love with this feature, when can we get it in?

-Steve
April 24, 2020
On 4/22/2020 5:04 AM, Manu wrote:
> [...]

The examples in the DIP are, frankly, too trivial to make a case for the ... feature.

So here's the challenge to everyone: a small (5?) collection of non-trivial motivating examples that show how it is done in D today, and how it is done with ..., and how much better the ... is.
April 24, 2020
On Friday, 24 April 2020 at 12:55:15 UTC, Nick Treleaven wrote:
> This syntax is an unfortunate inconsistency with your proposal, but how often is variadic UFCS used ATM?

It is fairly important for the string interpolation DIP that's pending. `i"foo".idup` would expand to a UFCS tuple call in my proposal.

I might be able to adapt without it anyway.... but I do think T.foo() going silently to <foo(T[0]), foo(T[1])> is not ideal.

I can live with the `T.foo()...` syntax though I'm not crazy about it either.

The idea is good though. And cutting out the library thing means it can be used in more contexts too (no more forcing CTFE due to passing them as compile-time params!)
April 24, 2020
On 4/24/20 4:55 PM, Walter Bright wrote:
> On 4/24/2020 10:10 AM, Steven Schveighoffer wrote:
>> I think the ellipsis version is superior simply because it has more expressive power (see my posts elsewhere). An ideal proposal would allow all things to be handled within one expression, but the ellipsis is better in that it is unambiguous and does not break code.
> 
> My issue is finding the best way to do this. Adding powerful syntax "just because we can" leads to an unusable language. Queue my opposition to AST macros.

It's not just the expressive power, but the simple expressiveness of the array syntax is missing. There are too many ambiguities without adding syntax because expressions that use tuples can already work with them.

The simplest is "make me an array of all these values"

[tup * 2]

But I don't see how the compiler knows which parts of the expression are part of the expansion, and which parts aren't. I would fully expect the above to result in:

([tup[0] * 2], [tup[1] * 2], ...)

And if it doesn't, then the compiler is making odd arbitrary decisions as to which part of the expression is expandable.

Which means, essentially, the odd bizarre cases are doable, but the useful ones are not.

The ellipsis doesn't do much in terms of functionality, but it gives you a place to tag "this is what I want you to expand", so the compiler makes the right decisions.

>> And actually, with a staticIota-like thing you could do every third tuple member quite easily.
>>
>> alias everyThird = staticIota!(2, Tup.length, 3); // start, end, step
>>
>> alias everyThirdMember = (Tup...[everyThird])...;
> 
> Touche. Well done.
> 
> Let's not fall into the mode of only looking at the way C++ did it and not seeing other ways. C++ has problems (like not having arrays) that lead it in different directions for solving array-like problems.

I'm fully on board with breaking with C++ when it doesn't make sense. In the case of the array syntax, I think it's just not workable.

> What do other languages do? How are things like this expressed in mathematics?


Math is a great inspiration: https://en.wikipedia.org/wiki/Sequence

Lots of positional notation, but you can see it uses an ellipsis to denote "continues on".

The question is, how can we denote "apply this expression to every element using these tuples". That's the goal. I don't know how we can do it with any more brevity or readability than adding a specific operator for it or adding a symbol for it.

> 
> 
>> staticIota is kind of another primitive that is super-useful, and would be easy for the compiler to provide.
> 
> Where does one stop in adding operators to the language?

You can do staticIota without language help. Just like you can do ALL of this without language help. The point where you stop is when it doesn't give you a 10-50x speedup in compilation and 50% reduction in memory by letting the compiler take care of it.

Note that I'm not saying we can't do staticIota in library code. But really, "count from 0 to N" is such a simple thing for a computer to do, it seems really wasteful to have to do it via symbol tables and CTFE.

> Another approach to resolving the original problem (template instantiation bloat) is for the compiler to recognize templates like AliasSeq as "special" and implement them directly.
> 
> 
> For example, AliasSeq is defined as:
> 
>    template AliasSeq(TList...) { alias AliasSeq = TList; }
> 
> This pattern is so simple it can be recognized by the compiler. (Having std.meta.AliasSeq being a special name known to the compiler is not necessary.)
> The compiler already recognizes certain patterns, such as the expression that does a rotate, and then generates the CPU rotate instruction for it.

Not arguing with that. The faster D can get, the better, regardless of whether we add any other syntax features.

One thing I would say is that AliasSeq is going to be identical for every instantiation, so there is no reason to store it in the symbol table. Just generate it every time, and then you cut down on a ton of template memory usage. Same thing could be said for a staticIota.

-Steve
April 24, 2020
On Friday, 24 April 2020 at 20:55:19 UTC, Walter Bright wrote:
>
> Another approach to resolving the original problem (template instantiation bloat) is for the compiler to recognize templates like AliasSeq as "special" and implement them directly.
>
>
> For example, AliasSeq is defined as:
>
>   template AliasSeq(TList...) { alias AliasSeq = TList; }
>
> This pattern is so simple it can be recognized by the compiler. (Having std.meta.AliasSeq being a special name known to the compiler is not necessary.)
> The compiler already recognizes certain patterns, such as the expression that does a rotate, and then generates the CPU rotate instruction for it.

AliasSeq itself is not the problem at all.
The problem is when operating on AliasSeq you need to instantiate new templates to keep them in a "TypeExp" state. Where they can still be manipulated as lists.
You detect operations you might do on type lists and implement them as special cases in the compiler, but then you end of with many specialzed cases which have to treated diffrently.
You push complexity from the langauge into the compiler implementation details.
And that's where it shouldn't be because who is going to verify that.
People here who are familiar with the c++11 syntax have already found bugs.
That won't be possible if it's an unspecified set of rewrites that take place in an unspecified stage.
April 24, 2020
On 4/24/2020 1:55 PM, Walter Bright wrote:
> How are things like this expressed in mathematics?

I did a little research:

    ∀xP(x)

means:

    for all x in P(x)

or in ... notation:

    P(x...)
April 24, 2020
On 4/24/2020 3:21 PM, Walter Bright wrote:
> I did a little research:

Actually, Andrei did.
April 24, 2020
On 4/24/2020 1:49 AM, Manu wrote:
> static foreach is not an expression, and it's very hard to involve those result calls in some conjunction. Expand that code to || them together... it gets ugly real fast.
> I wouldn't have wasted my time writing this DIP and a reference implementation if static foreach was fine.

This is why I suggested in the "Challenge" thread that these need to be motivating examples in the proposed DIP, not the ones that are in there, which are not particularly motivating.