D needs a type expression syntax

Quirin Schroll qs.il.paperinik at gmail.com
Fri May 12 17:13:51 UTC 2023


**This is 100% off-topic now. This should be in its separate 
thread.**

On Thursday, 11 May 2023 at 11:38:08 UTC, Timon Gehr wrote:
> On 08.05.23 13:21, Petar Kirov [ZombineDev] wrote:
>> On Sunday, 7 May 2023 at 07:35:22 UTC, Timon Gehr wrote:
>>> [..]
>>>
>>> Having `T[2]` be a tuple type `[T,T]` is one of those things 
>>> that initially sound good enough on paper until you actually 
>>> examine the existing array semantics that D already has and 
>>> try to square them with tuple semantics. It does not work at 
>>> all. D would need to break arrays. It's a pipe dream.
>>>
>> 
>> Can you explain in more detail which parts of the language 
>> make the unification impossible currently? Or to phrase the 
>> question differently, in a hypothetical D3 language what 
>> (breaking) changes we would need to do relative to D2 to make 
>> this possible?
>
> I am not sure it is desirable if arrays are not all value types.
>
> - If sliced, `T[n]` results in a `T[]`. Tuple slices should not 
> decay into by-reference array slices randomly based on whether 
> element types match. Also affects array assignment syntax and 
> array operations.

There’s two kinds of slices: `xs[]` and `xs[i .. j]`.

The first one should always return a slice type, i.e. `T[]` for 
some `T`.

Of course, you can’t slice a heterogeneous tuple like. You can’t 
slice it with run-time indices either. With indices known at 
compile-time, the result can be a tuple, but it won’t be, unless 
specifically asked to. It generally would become a slice, like 
for arrays:
```d
int[5] xs;
auto   ys = xs[2..4]; // typeof(ys) == int[]
int[2] zs = xs[2..4]; // You get int[2] if you ask for it.
```

If you change the type of `xs` to `Tuple!(long, int, int, int, 
int)` all of that should still work, but if you also replace e.g. 
`2` with a run-time variable, it would not work anymore.

> - Type of array literals is `T[]` (uniform element type, 
> dynamic length, reference type). It makes very little sense to 
> have this and also have `[int, string]` be a tuple type.

The actual type of a “braced literal” (let’s call it that) is 
neither `T[]` nor `T[n]`. It’s something internal to the 
compiler, something the D grammar has no syntax for.

It obviously isn’t `T[n]` because if you ask a `T[n]` its type, 
it answers `T[n]`.
It is *not* a `T[]` wither because – even though if you ask its 
type it answers `T[]` – it can do things a `T[]` generally can’t 
do: It can be converted to a `T[n]` if requested to, it even 
infers `n`; a general `T[]` can’t do that.

“The type of braced literals” is a type of its own.

Why couldn’t a “braced literal” be extended so that it converts 
to a tuple if asked to? The only new thing would be that there 
would be literals that you can’t initialize an `auto` variable 
with, but you can initialize a variable with it if its type is a 
tuple. That is because `auto` never infers static array types and 
likewise never infers tuple types, thus `auto` requires them to 
become a slice and they would need a common type for that. If 
there is none it’s a compile-error.

The same way we have 
[`staticArray`](https://dlang.org/library/std/array/static_array.html) that makes literals become a static array type, we can have `asTuple` that makes literals become tuple types:
```d
auto xs = [1, 2.0];             // typeof(xs) == double[]
auto ys = [1, 2.0].staticArray; // typeof(ys) == double[2]
auto zs = [1, 2.0].asTuple;     // typeof(zs) == [int, double]

auto bad = [new Object, 1]; // no common type for T[] of Object 
and int.
[Object, int] good1 = [new Object, 1]; // no type inference → no 
common type needed
auto good2 = [new Object, 1].asTuple; // typeof(good2) == 
typeof(good1)
```

Its implementation would be really simple:
```d
auto asTuple(Ts...)([Ts...] tuple) => tuple;
// staticArray for comparison:
auto staticArray(T, size_t n)(T[n] array) => array;
```

> - Tuple indices (presumably should) need to be statically 
> known, array indices do not. Having `[T, S] t; e = t[i]` 
> evaluate `i` at compile time iff `!is(T == S)` is potentially 
> surprising behavior.

It sounds weird, but there is a kind of spectrum between truly 
heterogeneous tuples and arrays. There’s tuples with varying 
degrees of homogeneity.

“Tuple indices need to be statically known” Yes, if the tuple is 
something like `Object` and `int` because they are totally 
unrelated. (It could be a sum type, though.) A tuple of `int` and 
`long` is kind of in-between and a tuple of `int` and `immutable 
int` is as close to an array as it could be without actually 
being one.

I’d phrase it the other way around: Every tuple supports indexing 
if the index is known at compile-time, and – for the record – 
that indexing is always by reference. A tuple would 
*conditionally* support run-time indexing depending on how 
similar its types are. Run-time indexing can be by reference or 
by value, again depending on how similar its types are. A static 
array viewed as a tuple of repeated type just so happens to 
satisfy the conditions such that run-time indexing by reference 
is always possible. Some non-array tuples allow for run-time 
indexing by reference as well. It’s simple Design by 
Introspection:
* If the types have a common reference type (e.g. `int` and 
`immutable(int)` can be referenced as `const(int)`), run-time 
indexing returns `const(int)` by reference.
* If the types have a common type (e.g. `int` and `long`), 
run-time indexing returns `long` by value.

The neat things is that if you expected run-time indexing 
behavior, but the index is available at compile-time, you might 
get a different type, but a type that’s better than the one you 
asked for.
If you expected indexing by value and it happens to be indexing 
by reference, the value is probably still copied; the exception 
is `auto ref`.

On the off-chance that you really needed a very specific 
behavior, you can ensure to get it. The simple syntax will give 
you the best it can given the circumstances, i.e. the types of 
the tuple and the compile-time-ness of the index.

> - `T[n]` has properties like `.ptr`, `dup`, `idup`. It seems a 
> bit weird to provide this for tuples and it's not clear what 
> they should do. I guess enabling them conditionally is an 
> option, but that seems wacky.

You generally don’t provide them for tuples. They would have 
those properties, if the types allow it. It’s just Design by 
Introspection. I’d not call that wacky.

> - `void[n]` and `void[]`. How do they fit into the tuple story?

My suggestion would be: `T[n]` is a shorthand for `[T, T, ...]` 
repeated `n` times, unless `T` is `void` or `n` is `0`. In those 
cases, `T[n]` is its own type. The first one is because a 
`void[n]` doesn’t store `n` values of type `void`, it’s untyped 
memory of `n` bytes; you can slice it, but not index it. The 
second one is because `T[0]` has an associated type. I can ask a 
`Tuple!(int, int)` “Are the types of your elements the same type 
and if so, what is that type?” and get the answer: Yes, `int`. If 
I ask `Tuple!(int, long)`, the answer is no. If I ask `Tuple!()`, 
the answer is yes, but there is no type!

We’d therefore have `T[0]` separate from the type of the empty 
tuple, at least if `T` is not `void`. I guess we can make 
`void[0]` the empty tuple type.

`void[]` is unrelated, it’s a slice, not an array or tuple.


More information about the Digitalmars-d mailing list