January 06, 2022
On 1/6/22 06:58, Paul Backus wrote:
>>
>> Why is that a surprise? You could similarly do something like:
>>
>> alias S=AliasSeq!(string);
> 
> Perhaps this is a better illustration:
> ...

No, it's the same thing. I understand what you are saying, I just don't agree it's important or likely to cause surprises in practice.

>      struct A {
>          B opArgs() { return B(); }
>      }
> 
>      struct B {}
> 
>      string fun(A) { return "A"; }
>      string fun(B) { return "B"; }
> 
>      void main() {
>          assert(fun(A()) == "A"); // fails
>      }
> 
> It's perfectly logical if you know about opArgs and have the definition of A in front of you, but it's extremely surprising and unintuitive if you don't.

Again, why is that different from the alias case? Furthermore, why would you use this feature willy-nilly in the first place without an intention to implement tuple semantics? "You can use this feature to write bad code" is a weak argument, it applies to every language feature.
January 06, 2022
On 1/6/22 08:14, bauss wrote:
> On Wednesday, 5 January 2022 at 22:42:14 UTC, Paul Backus wrote:
>> On Wednesday, 5 January 2022 at 07:28:41 UTC, Timon Gehr wrote:
>>>
>>> How about something like opArgs, dealing specifically with this case? (i.e., a function call `foo(x)` with a single argument is immediately rewritten to `foo(x.opArgs)` if `x` has a member `opArgs`, and this rewrite is applied exactly once.)
>>
>> This mechanism seems too powerful to me; for example, one could write code like the following:
>>
>>     struct S {
>>         string opArgs;
>>     }
>>
>>     string fun(S s) { return "S overload"; }
>>     string fun(string s) { return "string overload"; }
>>
>>     void main() {
>>         assert(fun(S()) == "S overload"); // fails
>>     }
>>
>> If there is to be any mechanism for automatic expansion of tuples, it should probably be narrow enough to avoid enabling surprises like this one.
> 
> You could just make sure that the root type is always used if an overload is available for it.
> 
> Just like if you did:
> 
> ```d
> struct S {
>    string opArgs;
>    alias opArgs this;
> }
> ```

No, if you want the root type, add a trailing comma to the declaration:

string fun(S s,){ return "S overload"; }
string fun(string s){ return "string overload"; }

(This matches unary tuple syntax (1,).)


January 06, 2022
On 1/6/22 06:58, Paul Backus wrote:
>>
> 
> Perhaps this is a better illustration:
> 
>      struct A {
>          B opArgs() { return B(); }
>      }
> 
>      struct B {}
> 
>      string fun(A) { return "A"; }
>      string fun(B) { return "B"; }
> 
>      void main() {
>          assert(fun(A()) == "A"); // fails
>      }

BTW, it should work like this:

```d
void foo(string x){}       // <- a string, not a tuple

void foo(string x,){}      // <- unary tuple
void foo((string,) t){}    // <- unary tuple

void foo(string x,int y){} // <- 2-tuple
void foo((string,int) t){} // <- 2-tuple
```

So perhaps your example would not even work that way; unary tuples shouldn't decay into the underlying type. The concept will need some fleshing out. Maybe it's indeed too hard to actually retrofit this into the language...
January 06, 2022
On 1/6/22 08:14, bauss wrote:
> 
> You could just make sure that the root type is always used if an overload is available for it.
> 
> Just like if you did:
> 
> ```d
> struct S {
>    string opArgs;
>    alias opArgs this;
> }
> ```


(But in general, yes, using `alias this` was my initial proposal and I once thought it's more or less viable, but Walter shot that down already.)
January 06, 2022
On Thursday, 6 January 2022 at 07:14:49 UTC, bauss wrote:
> On Wednesday, 5 January 2022 at 22:42:14 UTC, Paul Backus wrote:
>>
>> This mechanism seems too powerful to me; for example, one could write code like the following:
>>
>>     struct S {
>>         string opArgs;
>>     }
>>
>>     string fun(S s) { return "S overload"; }
>>     string fun(string s) { return "string overload"; }
>>
>>     void main() {
>>         assert(fun(S()) == "S overload"); // fails
>>     }
>>
>> If there is to be any mechanism for automatic expansion of tuples, it should probably be narrow enough to avoid enabling surprises like this one.
>
> You could just make sure that the root type is always used if an overload is available for it.
>
> Just like if you did:
>
> ```d
> struct S {
>   string opArgs;
>   alias opArgs this;
> }
> ```

Sure. You could make overload resolution consider a call that uses opArgs to be a "match with implicit conversion" [1], which would give it lower priority than an "exact match".

Which, IMO, perfectly illustrates the problem with opArgs: it's essentially a half-assed version of user-defined implicit conversions. Not powerful enough to do everything you'd want such a feature to do, but still powerful enough to shoot yourself in the foot.

[1] https://dlang.org/spec/function.html#function-overloading
January 06, 2022

On Thursday, 6 January 2022 at 13:18:56 UTC, Timon Gehr wrote:

>

BTW, it should work like this:

void foo(string x){}       // <- a string, not a tuple

void foo(string x,){}      // <- unary tuple
void foo((string,) t){}    // <- unary tuple

void foo(string x,int y){} // <- 2-tuple
void foo((string,int) t){} // <- 2-tuple

So perhaps your example would not even work that way; unary tuples shouldn't decay into the underlying type. The concept will need some fleshing out. Maybe it's indeed too hard to actually retrofit this into the language...

Actually I think you were on the right track in your earlier post, with this example:

void foo(int a, string b){} // pattern (int a,string b)
foo(1,"2"); // match tuple (1,"2")

i.e., the argument list itself should be treated like a tuple, and one could also write

auto tup = (1, "2");
foo(tup);

In fact, this is how tuples already work in D, just with nicer syntax--(1, "2") instead of tuple(1, "2").expand.

What the current language can't do, without using wrapper structs like std.typecons.Tuple, is nest tuples. In other words, the question we need to answer is perhaps better framed as "when aren't tuples expanded?"

January 07, 2022
On 12/29/2021 12:10 AM, Petar Kirov [ZombineDev] wrote:
> The typestate [1] design pattern. In languages that support some form of affine types [2] like Rust (*), this design pattern can be used to prevent classes of programmer errors at compile-time, while the best C++ and D can do is use `assert` at run-time to facilitate detecting them. Here's a few articles that showcase this in Rust:
> 
> * https://cliffle.com/blog/rust-typestate/
> * https://rustype.github.io/notes/notes/rust-typestate-series/rust-typestate-part-1
> * https://docs.rs/typestate/latest/typestate/
> 
> (*) Technically, affine types are about using a variable at most once. In Rust, if a type doesn't implement the Copy / Clone traits [3], variables of that type have move semantics - that is, they can't be used after they have been passed to a function that takes them by value (passing ownership). But they can be still passed multiple times to functions take them by reference (borrowing).
> 
> [1]: https://en.wikipedia.org/wiki/Typestate_analysis
> [2]: https://en.wikipedia.org/wiki/Substructural_type_system
> [3]: https://doc.rust-lang.org/std/marker/trait.Copy.html
> 
> 

Thanks, I did not know that.
January 07, 2022
On 1/3/2022 8:30 PM, Timon Gehr wrote:
> On 29.12.21 06:09, Walter Bright wrote:
>> @live currently does that for pointers, but not for non-pointers.
> It removes the variable from the scope? (And anyway, @live is a function annotation, which makes little sense.)

@live applies to all the contents of the function.

`scope` is ignored for non-pointer variables, whether they are @live or not.


>> What's the use case for it?
>> ...
> 
> This is just how moving things works. If you move it, it's no longer where it was. If the variable disappears, we don't have to bother with putting `x` into some safe default state. Some types can't even naturally support a safe default state. The simplest example is indeed a `struct` implementing a unique non-null pointer managing its own memory. (By whatever means you want, it does not even have to have any pointer members to implement such semantics.)
> 
>>
>>> int y=move(y); // ok
>>> ```
>>
>> I see what you mean, but since D disallows:
>>
>>      int x; { int x; }
>>
>> which prevents a number of bugs, so I can't see allowing that.
> 
> I don't see how that's related. It's two variables `y` whose lifetimes do not overlap. It's more like this:
> 
> {int x; } { int x; }
> 
> Which D allows.


You are pedantically correct, but it's one of those things that would be disallowed because it *looks* like a bug. There's no ambiguity to

    int x; { int x; }

either, it just *looks* like a bug, and a good chunk of the time it is not intended by the user and is a bug. I haven't seen a case yet where such code is needed.

I don't see a necessity to allow the y redeclaration.
January 07, 2022
Thanks, I know what that was, I just didn't know the jargon for it.
January 07, 2022
On 1/4/2022 3:36 PM, Timon Gehr wrote:
> On 04.01.22 08:52, Walter Bright wrote:
>> On 1/3/2022 10:37 PM, Timon Gehr wrote:
>>> If `foo(int, int)` is defined to be literally the same as `foo(int[2])`, there is no conversion. But anyway, I guess you'd also consider anything that would make this work an "implicit conversion"?
>>>
>>> double foo(int x,string y);
>>>
>>> writeln([(1,"2"),(1,"3"),(3,"4")].map!foo);
>>
>> Currently, tuples can map right on to argument lists, there is no conversion. This works because a tuple in D isn't really a type at all. It's just a collection of expressions.
>> ...
> 
> But in the example above, those so-called "tuples" would expand right into the array literal and cause an error due to incompatible types. This can't work if `(1,"2")` etc. auto-expand. Furthermore, to put them into an array, they need to have an address.

I know. It's a problem.


>>> If so, maybe one way to make _some_ progress on this is to have a DIP specifically for destructuring, without adding any new types?
>>
>> I'm not sure what destructuring,
> 
> Currently working with tuples (including actual tuples that are actual expressions, e.g., Phobos tuples) is inconvenient mostly (but not only) because you can't easily expand them into multiple named variables.
> 
> ```d
> auto (col1, col2) = table.query!((ref row)=>tuple(row.column1,row.column2))(key);
> ```
> 
> Proposal 1 here:
> https://github.com/tgehr/DIPs/blob/tuple-syntax/DIPs/DIP1xxx-tg.md
> 
> Proof-of-concept implementation here:
> https://github.com/tgehr/dmd/commit/8ad53b0891d3353dd96711595c06326ac0195d1b
> https://github.com/tgehr/dmd/commit/a0f59f93ab3727c93585dc658444860926a4b2c6
> 
>> but yes, not adding new types.
> 
> That's what I thought, so the tuple DIP is based on lowering to structs.
> 
> What's the path forward to making this work?

I don't know. I'll have to look at your proposals.

> 
> ```d
> double foo(int x,string y);
> 
> writeln([(1,"2"),(1,"3"),(3,"4")].map!foo);
> ```
> 
> My suggestion was proposal 2 here:
> https://github.com/tgehr/DIPs/blob/tuple-syntax/DIPs/DIP1xxx-tg.md
> 
> Proof-of-concept implementation here:
> https://github.com/tgehr/dmd/commit/bdb40fa96c471a7ace84596511a27ba3e803214f
> https://github.com/tgehr/dmd/commit/dad942117d85683e6234ef312c6d59be82f0c3a2
> 
> Note that Phobos tuples are implemented using an `alias this` to a built-in "tuple" of members:
> https://github.com/dlang/phobos/blob/master/std/typecons.d#L636-L655