April 25, 2020
On Saturday, 25 April 2020 at 10:27:38 UTC, Stefan Koch wrote:
>     mixin("enum x = (big_seq + 3)....length;");

Aaargh :-\

I'm not anti the `...` operator per se but seeing `....` in there (and yes, I understand what's happening!) is not very friendly to the reader.
April 25, 2020
On Saturday, 25 April 2020 at 10:39:24 UTC, Joseph Rushton Wakeling wrote:
> On Saturday, 25 April 2020 at 10:27:38 UTC, Stefan Koch wrote:
>>     mixin("enum x = (big_seq + 3)....length;");
>
> Aaargh :-\
>
> I'm not anti the `...` operator per se but seeing `....` in there (and yes, I understand what's happening!) is not very friendly to the reader.

I just wanted to show off that the parser accepts this properly.

April 25, 2020
On Saturday, 25 April 2020 at 10:27:38 UTC, Stefan Koch wrote:
> version(dotdotdot)
> {
>     mixin("enum x = (big_seq + 3)....length;");
> }

I have to say I don't find this notation intuitive for meaning.  `big_seq + 3` would imply that we're adding 3 to `big_seq` itself.  But without the surrounding ()... `big_seq + 3` is not a valid expression.

This is a case where the pythonic-style `x + 3 for x in big_seq` is, while more verbose, probably also a lot clearer.  It makes the difference between sequence and element much easier to see.

Compare with say `(big_seq_1 + big_seq_2)...`.  As we discussed off the message board, assuming the sequences are of the same length, this would result in the elementwise sum.  In other words it's about the same as:

    x1 + x2 for (x1, x2) in zip(big_seq_1, big_seq_2)

... but where the latter notation (though verbose) is pretty clear in what it means, the former is ambiguous because we have to know that `big_seq_1` and `big_seq_2` are both sequences.  In this case I used a clear naming scheme, but if in general I see:

    (a + b)...

then I have to know what both `a` and `b` are in order to understand clearly what will happen here.

If on the other hand I see:

    a + x for x in b

or

    x + b for x in a

or

    x1 + x2 for (x1, x2) in zip(a, b)

these are all much clearer in intent.

There are obviously other ways in which the `...` operator _is_ intuitive and maps well to some existing uses of similar notation.  But it would be nice if we could reduce ambiguity of intent.
April 25, 2020
On Sat, Apr 25, 2020 at 8:30 PM Walter Bright via Digitalmars-d < digitalmars-d@puremagic.com> wrote:

> On 4/25/2020 2:49 AM, Stefan Koch wrote:
> > This is supposed to make using staticMap cheap?
>
> No. It's to make AliasSeq cheap, to remove motivation for making a special syntactic construct for creating a tuple.
>

It's still un-fun to type AliasSeq!(), and I've never liked how it reads or
looks... but your patch is certainly welcome.
What we really need, is this:
  AliasSeq!(0 .. 10)

We should allow integer `..` range in a tuple to describe an iota. We allow it in foreach statements, it's a sadly missed opportunity for D.


April 25, 2020
On 4/24/20 5:26 PM, Walter Bright wrote:
> 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.

I have a couple: std.meta.NoDuplicates, and std.meta.Filter

template NewFilter(alias pred, T...)
{
    import std.meta : AliasSeq;
    static if(T.length == 1)
    {
        static if(pred!T)
            alias NewFilter = AliasSeq!(T);
        else
            alias NewFilter = AliasSeq!();
    }
    else
    {
        alias NewFilter = NewFilter!(pred, T)...;
    }
}

Original implementation here: https://github.com/dlang/phobos/blob/9844c34196c6f34743bfb4878d78cba804a57bf9/std/meta.d#L878-L898

Note that I've cut down the template instantiations from N *lg(N) instantiations of Filter to N instantiations of NewFilter, and only one level of recursion. Not only that, but...

If you have:

Filter!(Pred, SomeLongTuple);
NewFilter!(Pred, SomeLongTuple);

then

Filter!(Pred, SomeLongTuple[1 .. $]);

is going to produce a large amount of different instantiations (because of the divide and conquer recursion). Mostly only the leaves will be reused.

But

NewFilter!(PRed, SomeLongTuple[1 .. $]);

will generate ONE more instantiation, because all the rest will have been done from the first time.

Note that I have tested this in the proposed branch, with a few workarounds for syntax (Manu is working on this). And it passes the same unittests for Filter.

Now, for NoDuplicates, I have a personal interest in seeing this one be fixed:

template staticIota(size_t N)
{
    import std.meta : AliasSeq;
    string buildIota()
    {
        import std.range : iota;
        import std.format : format;
        return format("alias staticIota = AliasSeq!(%(%d,%));", iota(N));
    }
    mixin(buildIota());
}

template NewNoDuplicates(T...)
{
    import std.meta : AliasSeq;
    template getAlias(size_t idx) {
        alias list = T[0 .. idx];
        alias item = T[idx];
        import std.algorithm: canFind;
        static if([__traits(isSame, list, item)...].canFind(true))
            alias getAlias = AliasSeq!();
        else
            alias getAlias = AliasSeq!(T[idx]);
    }
    alias idxList = staticIota!(T.length)[1 .. $];
    alias NewNoDuplicates = AliasSeq!(T[0], getAlias!(idxList)...);
}

Original implementation here:
https://github.com/dlang/phobos/blob/9844c34196c6f34743bfb4878d78cba804a57bf9/std/meta.d#L405-L505

(yes, that's 100 lines, but includes docs and unittests)

Comparison of instantiations is laughable. I'm instantiating one template per element, plus the original NewNoDuplicates, vs. whatever happens in std.meta (there are several helper templates).

I believe the complexity of the original filter is N * N * lg(N), where as mine is N * N. If we had first-class type manipulation in CTFE, then we could get it down to N lg(N) by sorting the list. Also, I can probably avoid canFind and whatever it imports if I wanted to just search with a simple loop function for any true values in a local CTFE function.

I also tested this in the new branch, and it works, but again there are some bugs in the implementation Manu is working on, so the compilable version doesn't look as pretty.

Note, I think staticIota is going to be super-important if we get this ... (or whatever) change in, because you can do a lot of things now with a tuple of indexes. The above is crude, and probably can be improved, but note how we don't need all the vagaries of iota, because we can just use ... expressions to make whatever sequence we need out of 0 .. N.

That's why I think something like AliasSeq!(0 .. N) handled by the compiler would be tremendously useful.

-Steve
April 25, 2020
On 4/24/20 5:26 PM, Walter Bright wrote:
> 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.

also note, we can obsolete std.meta.anySatisfy or std.meta.allSatisfy, as we can use CTFE and ... expressions to do the same thing:

enum orig = anySatisfy!(someTemplate, someList);

enum changed = [someTemplate!(someList)...].any(true);

I propose a function any which takes a range and returns true if any element matches the provided one. And another one all(val) which does the same if all are the value. This gives us maximum flexibility.

Not only that, but that pattern can be used without temporary little `someTemplate` things, which are inevitably needed for anySatisfy.

If you look in my NoDuplicates example, I have used this pattern like:

static if([__traits(isSame, tuple, elem)...].any(true));

No templates needed.

-Steve
April 25, 2020
On Saturday, 25 April 2020 at 15:17:35 UTC, Steven Schveighoffer wrote:
> I propose a function any which takes a range and returns true if any element matches the provided one. And another one all(val) which does the same if all are the value. This gives us maximum flexibility.

Looks like std.algorithm.any and std.algorithm.all can already do this.
April 25, 2020
On Saturday, 25 April 2020 at 21:21:12 UTC, Walter Bright wrote:
> I don't know why it was changed to AliasSeq, which just grates on me as not indicating at all what it is.

People used to get confused over std.typecons.Tuple and the old TypeTuple. Moreover, TypeTuple can hold more than just types, so AliasSeq is arguably more descriptive.

Of course, there's still .tupleof on structs which is more of an AliasSeq than a Tuple so the joy of confusion remains.


> Not a bad idea. (.. is also used in case ranges)

eh case range is a different beast entirely. Though case 1..10: could perhaps expand to case 1: .. case 9:, I'd be OK having both.

But that shows a key difference: foreach range is exclusive, case range is inclusive.

> I've also thought that more templates than AliasSeq might be suitable for compiler short-cutting.

yessss

though this staticMap... thing combined with CTFE arrays can replace a bunch of these templates. i'm still letting some cases percolate through my brain but it is a nice day outside today so I don't wanna spend 3 hours typing examples yet.

yet ;)
April 25, 2020
On 4/25/2020 3:32 AM, Stefan Koch wrote:
> As I've said before: AliasSeq is not the slow part.

I agree. But as your benchmark showed, AliasSeq is worthwhile to do this optimization for. It doesn't take away motivation for ... at all.
April 25, 2020
On 4/25/2020 4:25 AM, Manu wrote:
> It's still un-fun to type AliasSeq!(), and I've never liked how it reads or looks... but your patch is certainly welcome.

It was originally TypeTuple!(), and still is in druntime. (Yes, the PR recognizes TypeTuple, too, as it recognizes the pattern, not the identifier.) I don't know why it was changed to AliasSeq, which just grates on me as not indicating at all what it is. Probably Tuple would be the best name.


> What we really need, is this:
>    AliasSeq!(0 .. 10)
> 
> We should allow integer `..` range in a tuple to describe an iota. We allow it in foreach statements, it's a sadly missed opportunity for D.

Not a bad idea. (.. is also used in case ranges)

I've also thought that more templates than AliasSeq might be suitable for compiler short-cutting.