January 11, 2021
On Monday, 11 January 2021 at 14:53:08 UTC, Ola Fosheim Grøstad wrote:
> On Monday, 11 January 2021 at 14:51:29 UTC, Ola Fosheim Grøstad wrote:
>> On Monday, 11 January 2021 at 14:03:39 UTC, Paul Backus wrote:
>>>     alias expand this;
>>
>> Hm... this does not allow me protect the fields from being changed. I also cannot use const since it is transitive and would make it impossible to return two references to mutable objects?
>>
>> Basically, the tuple itself should be immutable, but not the objects being referenced. I guess I could run over the types and add const if they are not references?
>
> But that would leave references mutable... so not the best solution. I kinda like the flexibility of __0, __1 and that it maps nicely to a regular struct.

I guess what I want is head-immutable (not even really head const).

January 11, 2021
On Monday, 11 January 2021 at 14:51:29 UTC, Ola Fosheim Grøstad wrote:
> Basically, the tuple itself should be immutable, but not the objects being referenced. I guess I could run over the types and add const if they are not references?

Why? I'd say that an `immutable(Tuple)` should be immutable, and a `Tuple` should be mutable, as is the case with literally every other type in D.
January 11, 2021
On Monday, 11 January 2021 at 17:48:13 UTC, Paul Backus wrote:
> Why? I'd say that an `immutable(Tuple)` should be immutable, and a `Tuple` should be mutable, as is the case with literally every other type in D.

Tuples are usually immutable, it brings more correctness.

January 11, 2021
On Monday, 11 January 2021 at 18:02:19 UTC, Ola Fosheim Grøstad wrote:
> On Monday, 11 January 2021 at 17:48:13 UTC, Paul Backus wrote:
>> Why? I'd say that an `immutable(Tuple)` should be immutable, and a `Tuple` should be mutable, as is the case with literally every other type in D.
>
> Tuples are usually immutable, it brings more correctness.

I agree that immutability has benefits, but I don't see why tuples should be singled out for special treatment in this regard. Why is immutability more important for tuples than for any other kind of data?
January 11, 2021
On Monday, 11 January 2021 at 19:25:06 UTC, Paul Backus wrote:
> On Monday, 11 January 2021 at 18:02:19 UTC, Ola Fosheim Grøstad wrote:
>> On Monday, 11 January 2021 at 17:48:13 UTC, Paul Backus wrote:
>>> Why? I'd say that an `immutable(Tuple)` should be immutable, and a `Tuple` should be mutable, as is the case with literally every other type in D.
>>
>> Tuples are usually immutable, it brings more correctness.
>
> I agree that immutability has benefits, but I don't see why tuples should be singled out for special treatment in this regard. Why is immutability more important for tuples than for any other kind of data?

Well, maybe not more important, but at least consistent with mathematics. :-)

I guess my real reason is that I am not ready to reimplement the type system and add one-level "readonly" semantics to the language at this point, so just preventing writable reference from leaking out seems one temporary approach that could work.

What I don't want is to have a tuple accidentally modified, so if one wants to modify the tuple then one should bind it to variables through destructuring? I guess that is a good reason? The caller of a function can decide to set up the return value from a function for mutation by destructuring the tuple.

I guess that would be the "idiomatic" pattern...



January 11, 2021
On Monday, 11 January 2021 at 19:25:06 UTC, Paul Backus wrote:
> I agree that immutability has benefits, but I don't see why tuples should be singled out for special treatment in this regard.

Oh, and another reason is that scalars can usually be passed by value with impunity, but you might want to pass tuples by reference as it could save you some copying in a significant way. And pass by reference would make the tuple vulnerable to mutation... (since we don't have head-const).


January 11, 2021
On Monday, 11 January 2021 at 20:36:30 UTC, Ola Fosheim Grøstad wrote:
> On Monday, 11 January 2021 at 19:25:06 UTC, Paul Backus wrote:
>> I agree that immutability has benefits, but I don't see why tuples should be singled out for special treatment in this regard.
>
> Oh, and another reason is that scalars can usually be passed by value with impunity, but you might want to pass tuples by reference as it could save you some copying in a significant way. And pass by reference would make the tuple vulnerable to mutation... (since we don't have head-const).

All of what you're saying applies equally well to any struct type as it does to tuples.

It sounds like what you really want is for D *in general* to have head-const, for all types. So there's no reason to force it on tuples in particular. Just write your DIP for 'readonly', and if it's accepted, you can write `readonly(Tuple)` and get the result you want for free.
January 11, 2021
On Monday, 11 January 2021 at 20:52:25 UTC, Paul Backus wrote:
> All of what you're saying applies equally well to any struct type as it does to tuples.

Sure.

> It sounds like what you really want is for D *in general* to have head-const, for all types. So there's no reason to force it on tuples in particular. Just write your DIP for 'readonly', and if it's accepted, you can write `readonly(Tuple)` and get the result you want for free.

More like head-immutable, but yes.

January 12, 2021
Ok, so I now have this, but I think maybe the switch could be turned into a static array by an reinterpret cast of "&expand[0]"? I would assume the layout would typically be "expand_field_0, expand_field_1 etc...


template Tuple(Types...){
    template same(){
        static foreach (i, dummy; Types) {
            static if (i + 1 < Types.length && !is(typeof(same) == bool)
                       && !is(Types[i] == Types[i + 1])) {
                enum bool same = false;
            }
        }
        static if (!is(typeof(same) == bool)) {
            enum bool same = true;
        }
    }

    struct Tuple {
        Types expand;
        alias expand this;

        static if (same!()) {
            auto opIndex(size_t i) {
                switch (i) {
                    static foreach (j; 0 .. Types.length) {
                        case j: return this.expand[j];
                    }
                    default: assert(0);
                }
            }
        }
    }
}


January 12, 2021
What about this?
No magic, but I don't know the performance impact.

```
import std.meta;
import std.conv;

template same(Types...)
{
    static if (Types.length >= 2)
    {
        static if (is(Types[0] == Types[$ - 1]))
        {
            const same = same!(Types[1 .. $]);
        }
        else
        {
            enum bool same = false;
        }
    }
    else
    {
        enum bool same = true;
    }
}

struct Tuple(Types...)
{

    static if (same!Types)
    {
        public Types[0][Types.length] elements;
        public this(Types[0][Types.length] elements...)
        {
            this.elements = elements;
        }
    }

    else
    {
        static foreach (int i, T; Types)
        {
            mixin("public " ~ T.stringof ~ " " ~ "elem" ~ i.stringof ~ ";");
        }
        public this(Types elements)
        {
            static foreach (int i, T; Types)
            {
                mixin("this.elem" ~ i.stringof ~ "=" ~ "elements[i]" ~ ";");
            }
        }
    }
}

int main()
{
    import std.stdio;

    auto homogenousTuple = Tuple!(int, int)(2, 3);
    writeln("homogenous tuple ", homogenousTuple.elements[0], ":",
            typeid(homogenousTuple.elements[0]), ":", homogenousTuple.elements[1],
            ":", typeid(homogenousTuple.elements[1]));
    auto heterogenousTuple = Tuple!(int, float)(2, 3);
    writeln("heterogenous tuple ", heterogenousTuple.elem0, ":", typeid(heterogenousTuple.elem0),
            ":", heterogenousTuple.elem1, ":", typeid(heterogenousTuple.elem1));
    return 0;
}
```

Problem is, the type arguments getn't inferred.