www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - D needs a type expression syntax

reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
**TL;DR:** If we make `TypeCtor` optional in the production rule 
`BasicType` → `TypeCtor`**`(`**`Type`**`)`**, the D type grammar 
can be improved and we have a way to solve the 14-year-old [issue 
2753](https://issues.dlang.org/show_bug.cgi?id=2753).

---



In D, as in most programming languages, there is the concept of a 
*[primary 
expression](https://dlang.org/spec/expression.html#primary_expressions),* that,
simply put, lets you put an arbitrary expression in parentheses giving you an
expression again. Without it, `(a + b) * c` wouldn’t even be expressible.

The scheme is like this:
 [`Expression`](https://dlang.org/spec/expression.html#Expression) →
[`PrimaryExpression`](https://dlang.org/spec/expression.html#primary_expressions)
 [`PrimaryExpression`](https://dlang.org/spec/expression.html#
rimary_expressions) →
**`(`**[`Expression`](https://dlang.org/spec/expression.html#Expression)**`)`**

Imagine you could only use parentheses where they’re needed and 
`(a * b) + c` would be an error, since `a * b + c` is in no way 
different. This is how D’s types behave. The [type 
grammar](https://dlang.org/spec/type.html#grammar) is quite a 
mouthful and I reworked it in the past to make it somewhat 
understandable for an outsider.



There’s one particular interaction that makes D’s types *almost* 
have a primary expression:
 [`Type`](https://dlang.org/spec/type.html#Type) → 
[`BasicType`](https://dlang.org/spec/type.html#BasicType)
 [`BasicType`](https://dlang.org/spec/type.html#BasicType) → 
[`TypeCtor`](https://dlang.org/spec/type.html#TypeCtor)**`(`**[`Type`](https://dlang.org/spec/type.html#Type)**`)`**

This means, a `Type` can be (among other options) just a 
`BasicType`, and a `BasicType` can be (among other options) a 
`TypeCtor` followed by a `Type` in parentheses. If we make the 
the `TypeCtor` optional, we get first-class type expression 
syntax. We should do this *today* and – taking advantage of it – 
do even more. (If you have experience with the parser, please let 
me know if this would be a difficult change. To me, it doesn’t 
seem like it would.)



Yes. This isn’t just an academic, puritan, inner-monk-pleasing 
exercise. D’s type syntax doesn’t let you express types that are 
100 % valid and useful and doesn’t let you clarify your 
intentions! Have you ever taken the address of a function that 
returns by reference? Normally, the function pointer type is 
written the same as a function declaration, just with the 
function name replaced by the `function` keyword:
```d
bool isEven  (int i) => i % 2 == 0;
bool function(int i) isEvenPtr = &isEven; // ok

ref int refId   (ref int i) => i;
ref int function(ref int i) refIdPtr = &refId; // Doesn’t parse!
```
You can declare `refIdPtr` with `auto` because the type of 
`&refId` is 100 % well-formed, it’s just a syntax issue spelling 
it out in code; if you `pragma(msg, typeof(refIdPtr))` you get:
```d
int function(ref int i) ref
```
Interesting where the `ref` is, isn’t it? Go ahead, try using 
*that* instead of `auto`. It doesn’t parse! And frankly, it 
shouldn’t; it’s confusing to read.

The reason is that the grammar works by max munch and we don’t 
have the type in isolation, it’s part of a declaration. The `ref` 
is parsed as a storage class for the declaration: It makes 
`refIdPtr` a reference to an object of type `int function(ref 
int)` – or, better, it would if it could. In this context, 
references aren’t allowed. Additionally, the type and value 
category of `&refId` don’t fit the declaration, but the parser 
doesn’t even get there.

One way to do it is to use an alias:
```d
alias FP = ref int function(ref int);
FP refIdPtr = &refId;
```
Why, then, does the alias definition of `FP` parse? Essentially 
because the alias declaration rules can boil down to this:
[`AliasDeclaration`](https://dlang.org/spec/declaration.html#AliasDeclaration)
→ **`alias`** *`Identifier`* **`=`** **`ref`**
[`Type`](https://dlang.org/spec/type.html#Type)
Simply put, alias declaration rules accept it as a special case.

We can use `auto`, so what’s the deal? The deal is that there are 
cases where `auto` cannot be used, e.g. in function parameter 
lists. A function with a function pointer parameter of type `FP` 
cannot be declared without an alias:
```d
void takesFP(ref int function(int) funcPtr) { pragma(msg, 
typeof(funcPtr)); }
```
This compiles, but doesn’t work as intended: The parameter 
`funcPtr` is of type `int function(int)` and taken by reference. 
Max munch reads `ref` and sees a 
[`ParameterStorageClass`](https://dlang.org/spec/function.html#Par
meterStorageClass), then it sees the
[`Type`](https://dlang.org/spec/type.html#Type) `int function(int)`. That’s
perfectly valid and one could want that.

Here’s the catch: We can solve a lot of syntax issues if we not 
only make [`TypeCtor`](https://dlang.org/spec/type.html#TypeCtor) 
optional (as suggested initially), but also allow `ref` as the 
initial part of a [`Type`](https://dlang.org/spec/type.html#Type) 
if followed by an appropriate 
[`TypeSuffix`](https://dlang.org/spec/type.html#TypeSuffix): the 
`function` and `delegate` ones. 
([Here](https://github.com/dlang/dlang.org/pull/3446) is the 
precise grammar change.)

This means, not only can you put types in parentheses to clarify 
your intent, it meaningfully affects parsing:
```d
void takesFP((ref int function(int)) funcPtr) { } // NEW! Doesn’t 
work yet.
```
Now, `ref` cannot be interpreted as a parameter storage class! It 
must be the first token of a 
[`Type`](https://dlang.org/spec/type.html#Type), which 
necessitates a function or delegate type, but that’s what we 
indeed have.

This also applies to return types:
```d
  ref int function(int)  makesFPbyRef() { }
(ref int function(int)) makesByRefFP() { }
```
According to max munch parsing, the first function returns an 
object of type `int function(int)` by reference, which is a 
function pointer that returns by value.
The second function returns an object of type `ref int 
function(int)` by value, which is a function pointer that returns 
by reference. As soon as the parser sees the opening parenthesis, 
it must parse a type.

The first of those should be deprecated in favor of this:
```d
ref (int function(int)) makesFPbyRef() { }
```

The same goes for parameters:
```d
void takesFP(ref  int function(int)  funcPtr) // Make this an 
error …
void takesFP(ref (int function(int)) funcPtr) // … and require 
this!
```

This is in the same spirit as [disallowing the nested 
lambdas](https://dlang.org/changelog/2.098.0.html#ambiguous-lambda) `=> { }`.
Together with that, we should deprecate applying [type
constructors](https://dlang.org/spec/type.html#TypeCtor) to function and
delegate types without clarification:
```d
  const Object  function()  f0; // Make this an error …
const (Object  function()) f1; // … and require this!
(const Object) function()  f2; // To differentiate from this.
  const(Object) function()  f3; // (Same type as f2)
```
We should do the same for type constructors as storage classes 
for non-static member function when used in front of the 
declaration:
```d
struct S
{
     const void action() { } // Make this an error …
     void action() const { } // … and require this!
}
```
D requires `ref` on the front, why should we have an issue with 
requiring that type constructors go to the end?



There would be another way to express `const(int)`: `(const 
int)`. Because `const(int)` is everywhere, it cannot be 
deprecated, and that’s fine. In my opinion, `(const int)` is 
better in every regard. A newcomer would probably guess correctly 
that `const(int)[]` is a mutable slice of read-only integers, but 
it’s no way as clear as `(const int)[]`. If we imagine D some 
years in the future, when everyone uses “modern-style types,” 
i.e. `(const int)[]`, seeing `const(int)[]` probably looks weird 
to you: `const` normally applies to everything that trails it, 
but here, because `const` is followed by an opening parenthesis, 
it applies precisely to what is in there, nothing more.
May 04 2023
next sibling parent reply zjh <fqbqrr 163.com> writes:
On Thursday, 4 May 2023 at 15:40:20 UTC, Quirin Schroll wrote:
 **TL;DR:** If we make `TypeCtor` optional in the production 
 rule `BasicType` → `TypeCtor`**`(`**`Type`**`)`**, the D type 
 grammar can be improved and we have a way to solve the 
 14-year-old [issue 
 2753](https://issues.dlang.org/show_bug.cgi?id=2753).
That's a great idea. You need a `dip`, and of course, it's best to have a tool that can automatically modify incorrect formats. It's also best to have a backup.
May 04 2023
parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Friday, 5 May 2023 at 01:23:15 UTC, zjh wrote:
 On Thursday, 4 May 2023 at 15:40:20 UTC, Quirin Schroll wrote:
 **TL;DR:** If we make `TypeCtor` optional in the production 
 rule `BasicType` → `TypeCtor`**`(`**`Type`**`)`**, the D type 
 grammar can be improved and we have a way to solve the 
 14-year-old [issue 
 2753](https://issues.dlang.org/show_bug.cgi?id=2753).
That's a great idea. You need a `dip`, and of course, it's best to have a tool that can automatically modify incorrect formats. It's also best to have a backup.
I have a very stupid question now: Why does a minor grammar change require a DIP? How do people determine what is an enhancement and what requires a DIP?
May 06 2023
next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 07/05/2023 3:55 AM, Quirin Schroll wrote:
 I have a very stupid question now: Why does a minor grammar change 
 require a DIP? How do people determine what is an enhancement and what 
 requires a DIP?
Minor grammar change != minor change. The implications of it as suggested in this thread is its a lot more involved than a tiny adjustment, which means DIP in this case.
May 06 2023
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 5/6/2023 8:55 AM, Quirin Schroll wrote:
 I have a very stupid question now: Why does a minor grammar change require a 
 DIP? How do people determine what is an enhancement and what requires a DIP?
It's not a stupid question at all. The reason is, it is definitely non-trivial, it will impact a lot of code with deprecations, people will want a strong explanation of why this is worthwhile, and enable a wider audience to look for risks and flaws. At a first reading, this looks like a worthwhile endeavor. But the thing is, what you've written is already pretty close to being a DIP. You've already done the hard part. One question: can this peacefully coexist with the existing syntax, before we push it with a deprecation?
May 06 2023
parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Sunday, 7 May 2023 at 00:14:35 UTC, Walter Bright wrote:
 On 5/6/2023 8:55 AM, Quirin Schroll wrote:
 I have a very stupid question now: Why does a minor grammar 
 change require a DIP? How do people determine what is an 
 enhancement and what requires a DIP?
It's not a stupid question at all. The reason is, it is definitely non-trivial, it will impact a lot of code with deprecations, people will want a strong explanation of why this is worthwhile, and enable a wider audience to look for risks and flaws.
Thank you for responding. The proposal can be summed up as: 1. Allow `TypeCtor` in the `TypeCtor(Type)` of `BasicType` syntax to be “mutable,” which is syntactically just nothing, i.e. allow `BasicType` to be `(Type)`. 2. Allow `ref` as the initial token for a type; such a type must be a `function` or `delegate` type to make sense. They’re even orthogonal: The first clearly does not depend on the second and even the second does not depend on the first, but they really shine together. My post was mostly about motivation, why this is useful/necessary and that, albeit it sounds big, is actually quite a small change.
 At a first reading, this looks like a worthwhile endeavor.
This is probably about changing a few lines of code in the parser, without any (big) additions.
 But the thing is, what you've written is already pretty close 
 to being a DIP. You've already done the hard part.

 One question: can this peacefully coexist with the existing 
 syntax, before we push it with a deprecation?
Yes. This was essentially asked by Basile B. and the answer is that the deprecations are entirely optional. They’re like deprecating `() => { }` (for creating nested lambdas) or, even closer, not allowing bit-operators and comparison operators without clarifying parentheses. I suggested them because I think that confusing syntax should only be allowed if truly the lesser of two evils. Demanding clarifying parentheses is not horrible breakage. We can 100% implement the feature today and discuss the deprecations in the next years or so.
May 08 2023
parent reply Nick Treleaven <nick geany.org> writes:
On Monday, 8 May 2023 at 17:02:07 UTC, Quirin Schroll wrote:
 The proposal can be summed up as:
 1. Allow `TypeCtor` in the `TypeCtor(Type)` of `BasicType` 
 syntax to be “mutable,” which is syntactically just nothing, 
 i.e. allow `BasicType` to be `(Type)`.
 2. Allow `ref` as the initial token for a type; such a type 
 must be a `function` or `delegate` type to make sense.

 They’re even orthogonal: The first clearly does not depend on 
 the second and even the second does not depend on the first, 
 but they really shine together.
Just noticed we have this form for function literals: ``` function RefOrAutoRefopt Typeopt ParameterWithAttributesopt FunctionLiteralBody2 ``` https://dlang.org/spec/expression.html#function_literals But for function types we have just: ``` TypeCtorsopt BasicType function Parameters FunctionAttributesopt ``` https://dlang.org/spec/type.html (inlining the *TypeSuffix* form) It seems we could add this form to *Type* for consistency with function literals: ``` function RefOrAutoRefopt Type ParameterWithAttributes ``` So this works: `void f(function ref int() g);` And add a similar type for delegates: ``` delegate RefOrAutoRefopt Type ParameterWithMemberAttributes ``` That would be a smaller impact change than parenthesized types.
May 13 2023
next sibling parent reply Nick Treleaven <nick geany.org> writes:
On Saturday, 13 May 2023 at 11:13:59 UTC, Nick Treleaven wrote:
 It seems we could add this form to *Type* for consistency with 
 function literals:
 ```
 function RefOrAutoRefopt Type ParameterWithAttributes
 ```
https://github.com/dlang/dmd/pull/15243
May 17 2023
parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Wednesday, 17 May 2023 at 12:08:41 UTC, Nick Treleaven wrote:
 On Saturday, 13 May 2023 at 11:13:59 UTC, Nick Treleaven wrote:
 It seems we could add this form to *Type* for consistency with 
 function literals:
 ```
 function RefOrAutoRefopt Type ParameterWithAttributes
 ```
https://github.com/dlang/dmd/pull/15243
I implemented my original approach, but some checks don’t work. You might want to look into it. This is the first time I’m working on the compiler. It’s probably some stupid error that I just cannot spot. I’d really appreciate some help with it. https://github.com/dlang/dmd/pull/15245
May 17 2023
prev sibling parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
*Note: The `opt`s made the grammar in quotations harder to read 
than necessary and aren’t essential to the quote, so I removed 
them.*

On Saturday, 13 May 2023 at 11:13:59 UTC, Nick Treleaven wrote:
 Just noticed we have this form for function literals:
 ```
 function RefOrAutoRef Type ParameterWithAttributes 
 FunctionLiteralBody2
 ```
 https://dlang.org/spec/expression.html#function_literals

 But for function types we have just:
 ```
  TypeCtors BasicType function Parameters FunctionAttributes
 ```
 https://dlang.org/spec/type.html (inlining the *TypeSuffix* 
 form)

 It seems we could add this form to *Type* for consistency with 
 function literals:
 ```
 function RefOrAutoRef Type ParameterWithAttributes
 ```
Technically, that would work. I disagree with “for consistency,” however. The current consistency is that the order of keyword and type make it a type or a literal. Optional elements aside, a function literal looks like this: ```d function int(int x) safe => x ``` and its type is: ```d int function(int x) safe ``` You see, if the keyword (`function` or `delegate`) is first and the type is second, it’s a literal (an expression); if the type is first and the keyword is second, it’s a type. It doesn’t take long to understand this duality, even if it’s not pointed out anywhere directly. I find this is really beautiful; it’s an elegant solution and easy to read. It’s a bad fortune of miracle proportions that C and C++ have a function pointer syntax (for C++ also member function pointer syntax) that is much harder to come up with _and_ a lot worse to read. The problem D has is that while ```d function ref int(ref int x) safe => x ``` is valid syntax for the literal, the corresponding ```d ref int function(ref int x) safe; ``` is not a valid type – except in an `alias` declaration, where it is. This is the heart of the issue. If I understand you correctly, you’d also allow ```d function int(int x) safe ``` as a type, that is, when the function doesn’t return by reference. (It would be really inconsistent if that wasn’t allowed.) The distinguishing factor then is what follows this whole long sequence of tokes. If it’s a brace, `do` or `=>`, then it’s an object, otherwise it’s a type, meaning that programmers (and likewise the parser) have to read it to the end to know if this is a type or an expression.
 So this works:
 `void f(function ref int() g);`
But for the type of a function returning by value, you’d then have two syntaxes, right? ```d function int() int function() ``` I don’t think this would be a great thing.
 […]
 That would be a smaller impact change than parenthesized types.
Depends on how you measure impact or what you consider small: * It requires a larger grammar change. * Thus, it probably requires more code in the parser. * It solves a very specific problem and introduces niche syntax. * It breaks with an existing principle. Considering that `ref int function()` already kind of is a type, namely in `alias` declarations, it doesn’t extend the trajectory of the current syntax, but goes in an entirely different direction. The smaller-impact solution is to make `ref int function()` a first-class type; practically, this isn’t enough because there are token sequences that could in principle be read two ways: `ref` being a the storage class of a function pointer parameter or indicating a function pointer parameter that returns by reference. These ambiguities are generally disambiguated by “max munch”. As in a lot of other cases, parentheses can disambiguate in the alternative direction. To be syntactically able to do so, types need primary expression syntax, which they even almost have. If the syntax like `(const int)` enabled by full-on type expression syntax are deemed a problem – which would be weird because e.g. `inout(const int)` is already allowed –, to still solve the `ref` returning function problem, instead of  [`BasicType`](https://dlang.org/spec/type.html#BasicType) → [`TypeCtor`](https://dlang.org/spec/type.html#TypeCtor)? **`(`**[`Type`](https://dlang.org/spec/type.html#Type)**`)`** we can still do  [`BasicType`](https://dlang.org/spec/type.html#BasicType) → [`TypeCtor`](https://dlang.org/spec/type.html#TypeCtor)? **`(`​`ref`** [`TypeCtor`](https://dlang.org/spec/type.html#TypeCtor)? [`BasicType`](https://dlang.org/spec/type.html#BasicType) **`function`** [`Parameters`](https://dlang.org/spec/function.html#Parameters) [`FunctionAttributes`](https://dlang.org/spec/function.html#FunctionAttributes)**`)`**  [`BasicType`](https://dlang.org/spec/type.html#BasicType) → [`TypeCtor`](https://dlang.org/spec/type.html#TypeCtor)? **`(`​`ref`** [`TypeCtor`](https://dlang.org/spec/type.html#TypeCtor)? [`BasicType`](https://dlang.org/spec/type.html#BasicType) **`delegate`** [`Parameters`](https://dlang.org/spec/function.html#Parameters) [`MemberFunctionAttributes`](https://dlang.org/spec/function.html#MemberFunctionAttributes)**`)`** I really don’t like this because the `ref` and the parentheses then require each other. As I stated earlier, it was as if `(A * B) + C` were invalid because the parentheses are redundant. As an example of the opposite approach, [Pony Lang](https://tutorial.ponylang.io/expressions/ops.html#precedence) has no operator precedence whatsoever, i.e. requires `(A * B) + C`.
May 17 2023
prev sibling parent Mike Parker <aldacron gmail.com> writes:
On Saturday, 6 May 2023 at 15:55:32 UTC, Quirin Schroll wrote:
 I have a very stupid question now: Why does a minor grammar 
 change require a DIP? How do people determine what is an 
 enhancement and what requires a DIP?
Given Walter's response, please hold off a bit before putting a DIP together. We're going to overhaul the DIP process. The DIP queue is closed to new DIPs until we're ready to launch it.
May 06 2023
prev sibling next sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Thursday, 4 May 2023 at 15:40:20 UTC, Quirin Schroll wrote:
 **TL;DR:** If we make `TypeCtor` optional in the production 
 rule `BasicType` → `TypeCtor`**`(`**`Type`**`)`**, the D type 
 grammar can be improved and we have a way to solve the 
 14-year-old [issue 
 2753](https://issues.dlang.org/show_bug.cgi?id=2753).

 [...]
Wouldn't making `ref` a TypeCtor solve the issue more simply ? While using parens is the natural way to disambiguate two different constructs, it seems that the only case that causes a problem is that we cannot express "return by ref" in function types. With `ref` as a TypeCtor: // a function that returns a function ptr that returns an int by ref ref(int) function(int) makesFPbyRef(); // function ptr as a parameter type thar returns an int by ref void takesFP(ref(int) function() funcPtr) // function ptr as a ref parameter type thar returns an int by ref void takesFP(ref ref(int) function() funcPtr) // normal variable type, can be a NOOP or a semantic error struct S { ref(int) member; } That being said, I understand that your proposal is not only about the ref issue but to make the grammar for Type nicer. Then, what is following will be a bit off-topic but, I'd like to bring the fact that it might be desirable to keep parens for builtin tuples, although it's been a while that the topic was not discussed here. In particular a TypeTuple made of a single Type would be hard to express properly.
May 04 2023
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 5 May 2023 at 02:30:32 UTC, Basile B. wrote:
 On Thursday, 4 May 2023 at 15:40:20 UTC, Quirin Schroll wrote:
 **TL;DR:** If we make `TypeCtor` optional in the production 
 rule `BasicType` → `TypeCtor`**`(`**`Type`**`)`**, the D type 
 grammar can be improved and we have a way to solve the 
 14-year-old [issue 
 2753](https://issues.dlang.org/show_bug.cgi?id=2753).

 [...]
Wouldn't making `ref` a TypeCtor solve the issue more simply ?
The main problem with making `ref` a TypeCtor is that it would allow semantically-invalid types like `ref(int)[]` and `Tuple!(ref(int), ref(int))` to parse.
May 04 2023
parent Walter Bright <newshound2 digitalmars.com> writes:
On 5/4/2023 9:34 PM, Paul Backus wrote:
 On Friday, 5 May 2023 at 02:30:32 UTC, Basile B. wrote:
 Wouldn't making `ref` a TypeCtor solve the issue more simply ?
The main problem with making `ref` a TypeCtor is that it would allow semantically-invalid types like `ref(int)[]` and `Tuple!(ref(int), ref(int))` to parse.
Yeah, arrays of references won't work.
May 07 2023
prev sibling parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Friday, 5 May 2023 at 02:30:32 UTC, Basile B. wrote:
 On Thursday, 4 May 2023 at 15:40:20 UTC, Quirin Schroll wrote:
 **TL;DR:** If we make `TypeCtor` optional in the production 
 rule `BasicType` → `TypeCtor`**`(`**`Type`**`)`**, the D type 
 grammar can be improved and we have a way to solve the 
 14-year-old [issue 
 2753](https://issues.dlang.org/show_bug.cgi?id=2753).

 [...]
Wouldn't making `ref` a `TypeCtor` solve the issue more simply?
Technically, the answer is yes, but practically and realistically, the answer isn’t just no, it is: absolutely not.
 While using parens is the natural way to disambiguate two 
 different constructs, it seems that the only case that causes a 
 problem is that we cannot express "return by ref" in function 
 types.
That was the starter. I looked at various ways you could express that. I’m convinced I once wrote a forum post asking for opinions on a syntax to propose, but now I cannot find it anymore (and that makes me doubt my memory). At some point, it randomly occurred to me that making D’s type syntax an expression syntax might naturally solve this – and working it out, it turns out it does.
 With `ref` as a `TypeCtor`:
 ```d
 // a function that returns a function ptr that returns an int 
 by ref
 ref(int) function(int) makesFPbyRef();
 // function ptr as a parameter type thar returns an int by ref
 void takesFP(ref(int) function() funcPtr)
 // function ptr as a ref parameter type thar returns an int by 
 ref
 void takesFP(ref ref(int) function() funcPtr)
 // normal variable type, can be a NOOP or a semantic error
 struct S { ref(int) member; }
 ```

 That being said, I understand that your proposal is not only 
 about the ref issue but to make the grammar for `Type` nicer.
It has `ref` as its main motivation. First, there’s a difference between making `ref` a `TypeCtor` and making it a type constructor. The first is grammar/syntax-only and the other is a semantic language construct. I’ll assume you mean both; listing it as a `TypeCtor` without actually following up semantically would definitely be possible, but very confusing. I’m proposing a grammar change that is mere addition. The deprecations I suggested are entirely optional and not required to make the syntax work. Also, I’m proposing a grammar change, no semantics change. Looking at `struct S { ref(int) member; }`, it really seems you propose that `ref` be a full-fledged type constructor: Look no further than C++ – they have a reference type constructor and in C++, essential (core!) language and library constructs don’t work with references: Pointer to a reference and (as a logical consequence) an array of references are not well-formed as a type. A non-compositional type system is the last thing D needs. I’ve thought long about this. **`ref(`***`Type`***`)`** as a syntax construct doesn’t get you far. ```d void takesFP(ref ref(int) function() funcPtr) ``` This is akin to member functions written like this: ```d const const(int) f() { … } ``` It’s not a nice read. ---
 Then, what is following will be a bit off-topic but, I'd like 
 to bring the fact that it might be desirable to keep parens for 
 builtin tuples, although it's been a while that the topic was 
 not discussed here. In particular a `TypeTuple` made of a 
 single `Type` would be hard to express properly.
This is no issue at all. With parentheses for tuple syntax, the 1-tuple for *values* must be expressed as `(value,)` because `(value)` is a plain expression, so it would just be consistent to require the trailing comma with *types* as well: `(int)` is `int`, but `(int,)` is a 1-tuple with an `int` component. For the record: I’ve objected to parentheses for tuples from the beginning. Parentheses are for grouping, not for constructing. My take on the tuples front was to look at static arrays as the special case of tuples that are homogeneous (the same type iterated), and generalize them for heterogeneous components: `int[2]` is a shorthand for `[int, int]`; and `[int, string]` is a heterogeneous tuple. You build such a tuple like you build an array: `[1, "abc"]`. Indexing with a run-time index is supported by Design by Introspection: A `[int, immutable int]` can give you `ref const(int)` access, a `[int, long]` can give you `long` access by value, but no `ref` access.
May 06 2023
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 5/6/2023 8:55 AM, Quirin Schroll wrote:
 For the record: I’ve objected to parentheses for tuples from the beginning. 
 Parentheses are for grouping, not for constructing. My take on the tuples
front 
 was to look at static arrays as the special case of tuples that are
homogeneous 
 (the same type iterated), and generalize them for heterogeneous components: 
 `int[2]` is a shorthand for `[int, int]`; and `[int, string]` is a
heterogeneous 
 tuple. You build such a tuple like you build an array: `[1, "abc"]`. Indexing 
 with a run-time index is supported by Design by Introspection: A `[int, 
 immutable int]` can give you `ref const(int)` access, a `[int, long]` can give 
 you `long` access by value, but no `ref` access.
I'd like Timon Gehr to weigh in on this!
May 06 2023
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 5/7/23 02:16, Walter Bright wrote:
 On 5/6/2023 8:55 AM, Quirin Schroll wrote:
 For the record: I’ve objected to parentheses for tuples from the 
 beginning. Parentheses are for grouping, not for constructing. My take 
 on the tuples front was to look at static arrays as the special case 
 of tuples that are homogeneous (the same type iterated), and 
 generalize them for heterogeneous components: `int[2]` is a shorthand 
 for `[int, int]`; and `[int, string]` is a heterogeneous tuple. You 
 build such a tuple like you build an array: `[1, "abc"]`. Indexing 
 with a run-time index is supported by Design by Introspection: A 
 `[int, immutable int]` can give you `ref const(int)` access, a `[int, 
 long]` can give you `long` access by value, but no `ref` access.
I'd like Timon Gehr to weigh in on this!
There is no conflict between the two features in the first place. I am in favor of allowing types in arbitrary expressions, as well as parentheses within types. My own D frontend already had a proof of concept implementation of that. [1] It also does not affect anything in my fork of DMD that already has some tuple support [2]: in particular a single-element tuple is indicated with a trailing comma, like `(int,) x = (1,);`. Having `(int) x = (1);` mean `int x = 1` would be fully consistent and unsurprising. [1] https://github.com/tgehr/d-compiler/ [2] https://github.com/tgehr/dmd/tree/tuple-syntax About the attack on standard tuple syntax with parentheses: 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. It's not that I don't get that fixed-length arrays and tuples (and variable-length-arrays and argument lists etc) can be fully unified in principle (I have done it in some of my own languages [3]), it's just that it cannot really be done well as an afterthought. [3] https://github.com/eth-sri/silq def rev[τ:*,n:!ℕ](xs:τ^n){ for i in 0..n div 2{ (xs[i],xs[n-1-i]):=(xs[n-1-i],xs[i]) } return xs; } def main(){ t:=(1,(2,3)): !ℕ×!ℕ^2; a:=(1,2,4,3): !ℕ^4; assert(rev(a)=(3,4,2,1)); assert(rev(a,)=(a,)); assert(rev(1,2,3)=(3,2,1)); n:=2+measure(H(0:𝔹)); // (n is 2 or 3 with equal probability) b:=vector(n,0:!ℕ); for i in 0..n{ b[i]=i; } assert(rev(rev(b))=b); }
May 07 2023
next sibling parent reply Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
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?
May 08 2023
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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. - 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. - 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. - `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. - `void[n]` and `void[]`. How do they fit into the tuple story?
May 11 2023
next sibling parent reply Dukc <ajieskola gmail.com> writes:
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. [snip]
Good points. I'll add: - `sizeof(T[0]) == 0`, but `sizeof(Tuple!()) == 1`. I'm not sure why.
May 11 2023
next sibling parent Basile B. <b2.temp gmx.com> writes:
On Thursday, 11 May 2023 at 13:51:28 UTC, Dukc wrote:
 Good points. I'll add:

 - `sizeof(T[0]) == 0`, but `sizeof(Tuple!()) == 1`. I'm not 
 sure why.
That sounds correct because tuples are structs with array semantics. Empty struct size is one, so should be the size of an empty tuple.
May 11 2023
prev sibling next sibling parent reply Nick Treleaven <nick geany.org> writes:
On Thursday, 11 May 2023 at 13:51:28 UTC, Dukc wrote:
 `sizeof(Tuple!()) == 1`. I'm not sure why.
The spec says `sizeof` for an empty struct is implementation defined: https://dlang.org/spec/struct.html#struct_layout
May 12 2023
parent =?UTF-8?Q?Joao_Louren=C3=A7o?= <jlourenco5691 gmail.com> writes:
It also says:

 2. Structs with no fields of non-zero size (aka *Empty Structs*) have a
size of one byte. On Fri, May 12, 2023, 7:01 PM Nick Treleaven via Digitalmars-d < digitalmars-d puremagic.com> wrote:
 On Thursday, 11 May 2023 at 13:51:28 UTC, Dukc wrote:
 `sizeof(Tuple!()) == 1`. I'm not sure why.
The spec says `sizeof` for an empty struct is implementation defined: https://dlang.org/spec/struct.html#struct_layout
May 12 2023
prev sibling parent reply Max Samukha <maxsamukha gmail.com> writes:
On Thursday, 11 May 2023 at 13:51:28 UTC, Dukc wrote:
 `sizeof(Tuple!()) == 1`. I'm not sure why.
A misfeature copied from C++ ("two instances of an empty class must have different addresses"). There are workarounds, such as like making the struct 'extern(C)', but they are ugly and unreliable.
May 12 2023
parent Max Samukha <maxsamukha gmail.com> writes:
On Saturday, 13 May 2023 at 04:16:12 UTC, Max Samukha wrote:
 such as like
I dream of a utopian future, in which humanity will find ways to edit forum posts.
May 12 2023
prev sibling parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
**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.
May 12 2023
prev sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Sunday, 7 May 2023 at 07:35:22 UTC, Timon Gehr wrote:
 On 5/7/23 02:16, Walter Bright wrote:
 [...]
There is no conflict between the two features in the first place. [...]
In my opinion tuples are more like struct... I dont't see how they are related to arrays.
May 09 2023
parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Tuesday, 9 May 2023 at 20:37:53 UTC, Basile B. wrote:
 On Sunday, 7 May 2023 at 07:35:22 UTC, Timon Gehr wrote:
 On 5/7/23 02:16, Walter Bright wrote:
 [...]
There is no conflict between the two features in the first place. [...]
In my opinion tuples are more like struct... I dont't see how they are related to arrays.
What is a `T[2]`? Something that contains two `T`s, accessed by index, otherwise unstructured. What is a `Tuple!(T, T)`? Something that contains two `T`s, accessed by index, otherwise unstructured. Can you see it now? (If you thought I meant `T[]`, that is indeed unrelated to tuples. But `T[]` isn’t an array, it’s a slice.)
May 10 2023
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 5/6/23 17:55, Quirin Schroll wrote:
 
 For the record: I’ve objected to parentheses for tuples from the 
 beginning. Parentheses are for grouping, not for constructing.
',' is the constructor...
May 07 2023
prev sibling next sibling parent reply Nick Treleaven <nick geany.org> writes:
On Thursday, 4 May 2023 at 15:40:20 UTC, Quirin Schroll wrote:
 ref int refId   (ref int i) => i;
 ref int function(ref int i) refIdPtr = &refId; // Doesn’t parse!
 ```
 You can declare `refIdPtr` with `auto` because the type of 
 `&refId` is 100 % well-formed, it’s just a syntax issue 
 spelling it out in code; if you `pragma(msg, typeof(refIdPtr))` 
 you get:
 ```d
 int function(ref int i) ref
 ```
 Interesting where the `ref` is, isn’t it? Go ahead, try using 
 *that* instead of `auto`. It doesn’t parse! And frankly, it 
 shouldn’t; it’s confusing to read.
It could be trailing return type syntax: ```d function(ref int i) -> ref int refIdPtr; ``` Then we don't need the `alias A = ref T` rule, and we can keep parentheses for expressions (including tuples in future). Trailing return is much easier to read, when you read a function signature the return type is not as salient as the name or the parameter list (particularly as there's no overloading by return type). The return type gets in the way, especially when its a complex type: ``` some_package.Foo!(const(ElementType!R)[]) oh_hi(long parameter, list following) ``` Some people also recommend putting attributes after the parameter list rather than before the declaration for this reason. Imagine in an IDE seeing a list of function overloads, if they were shown with trailing return syntax it would be much easier to find the overload you want.
 ```d
  const Object  function()  f0; // Make this an error …
 const (Object  function()) f1; // … and require this!
 (const Object) function()  f2; // To differentiate from this.
  const(Object) function()  f3; // (Same type as f2)
 ```
I don't get what the issue is with f0 and f3, aren't they clear enough?
 We should do the same for type constructors as storage classes 
 for non-static member function when used in front of the 
 declaration:
 ```d
 struct S
 {
     const void action() { } // Make this an error …
     void action() const { } // … and require this!
 }
 ```
 D requires `ref` on the front, why should we have an issue with 
 requiring that type constructors go to the end?
Amen, we should have done this long ago IMO. A needless hindrance for those learning D.
May 05 2023
next sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Friday, 5 May 2023 at 15:11:50 UTC, Nick Treleaven wrote:
 On Thursday, 4 May 2023 at 15:40:20 UTC, Quirin Schroll wrote:
 [...]
It could be trailing return type syntax: ```d function(ref int i) -> ref int refIdPtr; ``` Then we don't need the `alias A = ref T` rule, and we can keep parentheses for expressions (including tuples in future). [...]
IMO the big problem here, so with `const` is that const cannot be put for an hidden parameter... That's essentially why this strange syntax exists.
May 05 2023
parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Friday, 5 May 2023 at 16:26:38 UTC, Basile B. wrote:
 On Friday, 5 May 2023 at 15:11:50 UTC, Nick Treleaven wrote:
 On Thursday, 4 May 2023 at 15:40:20 UTC, Quirin Schroll wrote:
 [...]
It could be trailing return type syntax: ```d function(ref int i) -> ref int refIdPtr; ``` Then we don't need the `alias A = ref T` rule, and we can keep parentheses for expressions (including tuples in future). [...]
IMO the big problem here, so with `const` is that `const` cannot be put for an hidden parameter... That's essentially why this strange syntax exists.
To be honest, I don’t understand what you mean. Given a delegate (not a function) You can put `const` between the closing parenthesis and the arrow or after the arrow before the `BasicType`: ```d delegate(ref int i) const -> ref Object dg1; const delegate(ref int i) const -> ref Object dg2; delegate(ref int i) -> ref const Object dg3; ``` `dg1` returns a mutable Object, its context won’t be changed when invoked. `dg2` returns a mutable Object, its context won’t be changed when invoked; `dg2` cannot be reassigned. `dg3` returns a const Object. This is not an issue in any way.
May 06 2023
prev sibling parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Friday, 5 May 2023 at 15:11:50 UTC, Nick Treleaven wrote:
 On Thursday, 4 May 2023 at 15:40:20 UTC, Quirin Schroll wrote:
 ```d
 ref int refId   (ref int i) => i;
 ref int function(ref int i) refIdPtr = &refId; // Doesn’t 
 parse!
 ```
 You can declare `refIdPtr` with `auto` because the type of 
 `&refId` is 100 % well-formed, it’s just a syntax issue 
 spelling it out in code; if you `pragma(msg, 
 typeof(refIdPtr))` you get:
 ```d
 int function(ref int i) ref
 ```
 Interesting where the `ref` is, isn’t it? Go ahead, try using 
 *that* instead of `auto`. It doesn’t parse! And frankly, it 
 shouldn’t; it’s confusing to read.
It could be trailing return type syntax: ```d function(ref int i) -> ref int refIdPtr; ``` Then we don't need the `alias A = ref T` rule, and we can keep parentheses for expressions (including tuples in future).
I agree that trailing return types (TRT) are really well readable – in a language designed around them, which D isn’t. I find it irritating that the `-> Type` is followed by the variable name. In languages that have them, variables are declared by a keyword, then follows the name, then a separator and then comes the type. In that order, TRT makes sense.
 Trailing return is much easier to read, when you read a 
 function signature the return type is not as salient as the 
 name or the parameter list (particularly as there's no 
 overloading by return type). The return type gets in the way, 
 especially when its a complex type:
 ```
 some_package.Foo!(const(ElementType!R)[]) oh_hi(long parameter, 
 list following)
 ```
 Some people also recommend putting attributes after the 
 parameter list rather than before the declaration for this 
 reason.
I do that. I’ll never understand why people use them in front. And why `unittest` don’t work with trailing attributes. It’s inconsistent and annoying. I put parts of a declaration in their own line when they’re getting big: Big return type → own line. 1 big (template) parameter or lots of them → each its own line. Constraints and contracts → own line (always). It’s another thing what a documentation generator makes of them. ```d some_package.Foo!(const(ElementType!R)[]) oh_hi(R, T)( T parameter, ... ) const safe pure if (T.sizeof > 0) in (true) out(r; true) { … } ```
 Imagine in an IDE seeing a list of function overloads, if they 
 were shown with trailing return syntax it would be much easier 
 to find the overload you want.
Could be. When looking through overloads, the important part is the parameters. TRT are are a much greater language change, though, compared to making a token at a very specific place optional. Their grammar must really be implemented and maintained separately. Also, we get the same as C++: Two ways to declare a function.
 ```d
  const Object  function()  f0; // Make this an error …
 const (Object  function()) f1; // … and require this!
 (const Object) function()  f2; // To differentiate from this.
  const(Object) function()  f3; // (Same type as f2)
 ```
I don't get what the issue is with f0 and f3, aren't they clear enough?
With `f0`: Does `const` apply to the return value or the variable `f0`? I’m quite adept with D and I’m only 95% sure it applies to `f0`. 95 is not 100, and frankly, when it comes to syntax, it should be 100. With `f3`: Less of an issue, but basically the same. To someone new to D, it might be odd that `const` applies to something different if a parenthesis follows. `f1` and `f2` are beyond misapprehension. If you show any programmer what `const` applies to (return type or the declared variable), they’ll assume it’s a trick question because the answer is plain obvious.
May 06 2023
parent reply Nick Treleaven <nick geany.org> writes:
On Saturday, 6 May 2023 at 16:20:56 UTC, Quirin Schroll wrote:
 It could be trailing return type syntax:
 ```d
 function(ref int i) -> ref int refIdPtr;
 ```
 Then we don't need the `alias A = ref T` rule, and we can keep 
 parentheses for expressions (including tuples in future).
I agree that trailing return types (TRT) are really well readable – in a language designed around them, which D isn’t. I find it irritating that the `-> Type` is followed by the variable name. In languages that have them, variables are declared by a keyword, then follows the name, then a separator and then comes the type. In that order, TRT makes sense.
It's the same pattern as `int var;` - `Type identifier;`. Do you think it would cause parsing ambiguities? If not, maybe you read it as naming the return type? In languages that have named return types, I think a named return would be written with parentheses: `-> (Type identifier)`.
 I put parts of a declaration in their own line when they’re 
 getting big: Big return type → own line. 1 big (template)
OK but that wastes vertical space, particularly in an IDE popup window with a long list of overloads.
 TRT are are a much greater language change, though, compared to 
 making a token at a very specific place optional. Their grammar 
 must really be implemented and maintained separately.
Conceptually I think it would be simple: ``` (function | delegate) ParameterList Attributes? -> StorageClass Type StorageClass Identifier ParameterList Attributes? -> StorageClass Type ``` ```d const method() -> int; ```
 Also, we get the same as C++: Two ways to declare a function.
Modern C++ may be moving toward using TRT, at least Herb Sutter recommends it. The idea is to move to TRT over time. Linters could enforce it.
 ```d
  const Object  function()  f0; // Make this an error …
 const (Object  function()) f1; // … and require this!
 (const Object) function()  f2; // To differentiate from this.
  const(Object) function()  f3; // (Same type as f2)
 ```
I don't get what the issue is with f0 and f3, aren't they clear enough?
With `f0`: Does `const` apply to the return value or the variable `f0`? I’m quite adept with D and I’m only 95% sure it applies to `f0`. 95 is not 100, and frankly, when it comes to syntax, it should be 100.
You're right, `f1` is better. I thought I was used to const applying to the variable/parameter, but I just confused myself trying to get a parameter to work: ```d void f(const ref int function() p); ``` (I started thinking if the function could have leading const, ref would parse). Although if leading const on functions was deprecated there might be less confusion for the parameter case.
 With `f3`: Less of an issue, but basically the same. To someone 
 new to D, it might be odd that `const` applies to something 
 different if a parenthesis follows.
OK. I'm not sure if parenthesized types would complicate parsing.
May 10 2023
parent Nick Treleaven <nick geany.org> writes:
On Wednesday, 10 May 2023 at 13:30:10 UTC, Nick Treleaven wrote:
 If not, maybe you read it as naming the return type? In 
 languages that have named return types,
I meant named return value(s).
May 10 2023
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
This is written with Markdown, which my newsreader doesn't recognize. Here's a 
more readable link:

https://forum.dlang.org/thread/lxcjsxxamwwtkdpmjhze forum.dlang.org
May 06 2023
parent reply David Gileadi <gileadisNOSPM gmail.com> writes:
On 5/6/23 5:50 PM, Walter Bright wrote:
 This is written with Markdown, which my newsreader doesn't recognize. 
 Here's a more readable link:
 
 https://forum.dlang.org/thread/lxcjsxxamwwtkdpmjhze forum.dlang.org
If you happen to be using Thunderbird, I made an add-on that renders Markdown messages pretty well. https://addons.thunderbird.net/en-US/thunderbird/addon/render-markdown-messages/?src=search
May 07 2023
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 5/7/2023 10:56 AM, David Gileadi wrote:
 On 5/6/23 5:50 PM, Walter Bright wrote:
 If you happen to be using Thunderbird, I made an add-on that renders Markdown 
 messages pretty well.
 
 https://addons.thunderbird.net/en-US/thunderbird/addon/render-markdown-messages/?src=search
I actually found and installed that last night! Thanks, it works great!
May 10 2023
parent David Gileadi <gileadisNOSPM gmail.com> writes:
On 5/10/23 10:45 AM, Walter Bright wrote:
 On 5/7/2023 10:56 AM, David Gileadi wrote:
 On 5/6/23 5:50 PM, Walter Bright wrote:
 If you happen to be using Thunderbird, I made an add-on that renders 
 Markdown messages pretty well.

 https://addons.thunderbird.net/en-US/thunderbird/addon/render-markdown-messages/?src=search
I actually found and installed that last night! Thanks, it works great!
You're welcome!
May 10 2023
prev sibling parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
Given:
 [`Type`](https://dlang.org/spec/type.html#Type) → 
[`BasicType`](https://dlang.org/spec/type.html#BasicType)
 [`BasicType`](https://dlang.org/spec/type.html#BasicType) → 
**`(`**[`Type`](https://dlang.org/spec/type.html#Type)**`)`**


I have found one issue, but it falls under the category of Nobody 
Writes Such Code:
If a type `T` has a `static` indexing operator (`opIndex` or 
similar), then the token sequence `T[]` can denote a 
[`Type`](https://dlang.org/spec/type.html#Type) or an 
[`Expression`](https://dlang.org/spec/expression.html#Expression). If that
token sequence is itself in parentheses and not prefixed by a
[`TypeCtor`](https://dlang.org/spec/type.html#TypeCtor), then it currently
cannot parse as a [`Type`](https://dlang.org/spec/type.html#Type), but with the
proposed changes, it could, and in some circumstances, that would make valid
working code change meaning.

Example:
```d
enum isType(alias a) = is(a);

struct X
{
     static int opIndex()  safe => 0;
}

// Parsed as a type,  `X[]` is slice of `X`.
// Parsed as a value, `X[]` is `X.opIndex()`.

void main()  safe
{
     static assert(!isType!( 0 ));
     static assert( isType!(int));

     static assert( isType!( X[] ));
     static assert(!isType!((X[]))); // This would fail!
}
```

Again, nobody writes such code and relies on a pair of 
parentheses to make something a value that’s read by any human as 
a type. This is something that is even bad today. Nothing should 
change what it is just because you put it in a pair of 
parentheses.
May 09 2023
parent reply Tim <tim.dlang t-online.de> writes:
On Tuesday, 9 May 2023 at 17:15:41 UTC, Quirin Schroll wrote:
 I have found one issue, but it falls under the category of 
 Nobody Writes Such Code:
 If a type `T` has a `static` indexing operator (`opIndex` or 
 similar), then the token sequence `T[]` can denote a 
 [`Type`](https://dlang.org/spec/type.html#Type) or an 
 [`Expression`](https://dlang.org/spec/expression.html#Expression). If that
token sequence is itself in parentheses and not prefixed by a
[`TypeCtor`](https://dlang.org/spec/type.html#TypeCtor), then it currently
cannot parse as a [`Type`](https://dlang.org/spec/type.html#Type), but with the
proposed changes, it could, and in some circumstances, that would make valid
working code change meaning.
There are some other examples with similar problems: The code `X*[1]` could be both an expression and a type. As an expression X would need to implement opBinary for multiplication. As a type it would be an array to a pointer to X. The code `X*[0][0]` could also be both. As an expression it would multiply X with `[0][0]`, which is 0. As a type it would be an array of an array of a pointer to X. This example does not need operator overloading. A more complex example also uses `*` as a pointer dereference: `X**[Y][0]` This could be a type, if X is a type and Y is also a type or evaluates to an integer. If X is an integer and Y is a pointer to an integer, then it would be a valid expression.
May 09 2023
parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Tuesday, 9 May 2023 at 18:13:09 UTC, Tim wrote:
 On Tuesday, 9 May 2023 at 17:15:41 UTC, Quirin Schroll wrote:
 I have found one issue, but it falls under the category of 
 Nobody Writes Such Code:
 If a type `T` has a `static` indexing operator (`opIndex` or 
 similar), then the token sequence `T[]` can denote a 
 [`Type`](https://dlang.org/spec/type.html#Type) or an 
 [`Expression`](https://dlang.org/spec/expression.html#Expression). If that
token sequence is itself in parentheses and not prefixed by a
[`TypeCtor`](https://dlang.org/spec/type.html#TypeCtor), then it currently
cannot parse as a [`Type`](https://dlang.org/spec/type.html#Type), but with the
proposed changes, it could, and in some circumstances, that would make valid
working code change meaning.
There are some other examples with similar problems: The code `X*[1]` could be both an expression and a type. As an expression `X` would need to implement `opBinary` for multiplication. As a type it would be an array to a pointer to `X`.
To parse as a type, `X` *must* be a type. Therefore, to a type or expression depending on what to parse, `X` *must* be a type. To parse as an expression `X` it *must* be an aggregate type has a `static` member function template `opBinary` that can be instantiated as `opBinary!"*"` and called with `[1]`. It must be `static` because `X` must parse as a type if `X` is unambiguously a type or an expression. (Phrased in reverse, if `X` parses as an expression, but never as a type, then `X*[1]` cannot parse as a type.) If `X` is ambiguous itself, you just shift the problem down one layer.
 The code `X*[0][0]` could also be both. As an expression it 
 would multiply `X` with `[0][0]`, which is `0`. As a type it 
 would be an array of an array of a pointer to X. This example 
 does not need operator overloading.

 A more complex example also uses `*` as a pointer dereference: 
 `X**[Y][0]`
 This could be a type, if `X` is a type and `Y` is also a type 
 or evaluates to an integer.
 If `X` is an integer and `Y` is a pointer to an integer, then 
 it would be a valid expression.
To change meaning silently, `X` would have to be an aggregate type that implements `opBinary` as a `static` function. This is really important because Nobody Writes Such Code. I haven’t been able to come up with an example that necessitates a `static` operator function.
May 10 2023
parent Tim <tim.dlang t-online.de> writes:
On Wednesday, 10 May 2023 at 14:12:33 UTC, Quirin Schroll wrote:
 To change meaning silently, `X` would have to be an aggregate 
 type that implements `opBinary` as a `static` function. This is 
 really important because Nobody Writes Such Code.

 I haven’t been able to come up with an example that 
 necessitates a `static` operator function.
The parser does not know if X is a type or something else, because that is only determined later in semantic analysis. So the parser would need to prefer a type or expression. Semantic analysis could maybe change the chosen tree later if it is wrong. But you are right, that it would not change the meaning silently without static opBinary. If the grammar is changed, then the user would get an error message.
May 10 2023