February 28
On 2/28/2024 1:03 PM, Timon Gehr wrote:
> No, the idea is that the compiler enforces that it is indeed the last use and produces a compile-time error message if it cannot prove that it is the case.

DFA works with mathematical reliability (absent compiler bugs). The optimizer relies heavily on DFA; if DFA was unreliable the whole edifice will fall apart. Leave move vs copy to the compiler. The move vs copy/destroy choice is an optimization, and should be semantically thought of it that way.

> Yes. It still holds that one may want to make sure that a value is really moved at a given point. Sometimes this matters. Anyway, this is by far not the most important point.

If more language features are needed to work around bugs in the DFA, you've failed as a language designer/implementer. :-/

Last use DFA can be implemented in a mathematically correct manner. The downside to DFA is it slows down the compiler, which concerns me in adding it to the front end semantics. I'm guessing is that's a reason why Rust has a reputation for slow compiles. (C++ doesn't have an excuse!)


>> A nice feature of this is that the type of a variable can be changed on redeclaration. Note that Rust allows this.
> 
> This is a relatively common idiom in languages that support moves. It is annoying if you have to invent a new name for each intermediate result.
> 
> One use case would be type state:
> 
> File!(FileState.flushed) file = open("file.txt");
> File!(FileState.buffered) file = file.write("hello ");
> File!(FileState.buffered) file = file.writeln("world!");
> // file.close(); // compile time error
> File!(FileState.flushed) file = file.flush();
> file.close(); // moves "file"
> 
> // file.write("hello"); // compile time error
> 
> Assume you have some declarations like these and you want to comment out part of the statements. It would now be annoying to have to rename variables.
> 
> I.e., you want to use the same name for different versions of the same thing, similarly to how you do not have to change the name of a variable when assigning to it.

Interesting that you bring that up. I've been slowly leaning towards the "single assignment" style, where a variable is only assigned to once, when it is initialized. Sort of a "head const" thing. Some languages enforce this (can't remember which ones).

I find it makes code more readable.

I get the feeling that allowing not only the contents, but the type of the variable change after re-assignment makes for less coherent code.

I'm not sure why, but I find Rust code hard to read. Maybe that's part of it. I like the O/B system so much I implemented it in D (@live), not only that, but have begun adopting it as my own coding style. And it looks sooo much nicer in D syntax!


>> We already disallow shadowing declarations, and that has prevented a number of bugs at least in my own code (they're very difficult to spot with a visual check).
> 
> The reason why shadowing is error prone is that multiple variables with overlapping lifetimes are in scope and the compiler arbitrarily picks one of them. This case is different, as only one variable of the same name exists at any given time. This is not error prone.

I've made that error myself now and then, usually as a result of moving code lines about.

> Requiring unique names is more error prone in this case, as you can accidentally copy an older version of a variable.

I can't remember making that error :-/


> Anyway, this is not the most important thing, please do check out the points I initially included in my review. This point is just something I had forgotten to include.

Of course. This was just an easy to respond to issue.

But a caveat. I'm kinda swamped at the moment. I'm working on some cool stuff for the upcoming DConf. I also wrote the Move/Copy/Forward DIP before I worked on the O/B system for D. The whole Move/Copy/Forward needs to be studied in the context of how it fits in with O/B. This is going to need some careful study.
February 28
I do share your concern, Don. I worry that we know so well how C++ does it that we can't conceive of a better way. If we cannot do mucho better than C++ with this, why are we using D?

D already does some things so much better than C++ by thinking "dammit there's got to be a better way than this mess!"

Things like no ADL, decent modules, simple template syntax, easy to understand overloading, context-free grammar, slices, etc.
February 29
On Thursday, 29 February 2024 at 04:24:02 UTC, Walter Bright wrote:
> I do share your concern, Don. I worry that we know so well how C++ does it that we can't conceive of a better way. If we cannot do mucho better than C++ with this, why are we using D?
>
> D already does some things so much better than C++ by thinking "dammit there's got to be a better way than this mess!"
>
> Things like no ADL, decent modules, simple template syntax, easy to understand overloading, context-free grammar, slices, etc.

Try using DFA technology not during normal compilation in the compiler, but in an alternative checking mode that maintains compilation performance while allowing program-assisted analysis.
February 29
On Thursday, 29 February 2024 at 04:17:38 UTC, Walter Bright wrote:
> On 2/28/2024 1:03 PM, Timon Gehr wrote:
>> No, the idea is that the compiler enforces that it is indeed the last use and produces a compile-time error message if it cannot prove that it is the case.
>
> DFA works with mathematical reliability (absent compiler bugs). The optimizer relies heavily on DFA; if DFA was unreliable the whole edifice will fall apart. Leave move vs copy to the compiler. The move vs copy/destroy choice is an optimization, and should be semantically thought of it that way.

Not always. Some types have to be move-only.

February 29
On 2/29/24 05:17, Walter Bright wrote:
> On 2/28/2024 1:03 PM, Timon Gehr wrote:
>> No, the idea is that the compiler enforces that it is indeed the last use and produces a compile-time error message if it cannot prove that it is the case.
> 
> DFA works with mathematical reliability (absent compiler bugs). The optimizer relies heavily on DFA; if DFA was unreliable the whole edifice will fall apart. Leave move vs copy to the compiler. The move vs copy/destroy choice is an optimization, and should be semantically thought of it that way.
> 
>> Yes. It still holds that one may want to make sure that a value is really moved at a given point. Sometimes this matters. Anyway, this is by far not the most important point.
> 
> If more language features are needed to work around bugs in the DFA, you've failed as a language designer/implementer. :-/
> ...

This is not about catching bugs in the DFA. This is about allowing a reader of the code to not have to execute the DFA in their head in order to see that something is the last use.

> Last use DFA can be implemented in a mathematically correct manner.

No, it is undecidable. You can only implement a sound approximation.

> The downside to DFA is it slows down the compiler, which concerns me in adding it to the front end semantics. I'm guessing is that's a reason why Rust has a reputation for slow compiles. (C++ doesn't have an excuse!)
> ...

Last-use analysis on a control-flow graph can be implemented in an efficient manner using strongly connected components. It is even easier to compute on a structured program. Rust has no goto.

> 
>>> A nice feature of this is that the type of a variable can be changed on redeclaration. Note that Rust allows this.
>>
>> This is a relatively common idiom in languages that support moves. It is annoying if you have to invent a new name for each intermediate result.
>>
>> One use case would be type state:
>>
>> File!(FileState.flushed) file = open("file.txt");
>> File!(FileState.buffered) file = file.write("hello ");
>> File!(FileState.buffered) file = file.writeln("world!");
>> // file.close(); // compile time error
>> File!(FileState.flushed) file = file.flush();
>> file.close(); // moves "file"
>>
>> // file.write("hello"); // compile time error
>>
>> Assume you have some declarations like these and you want to comment out part of the statements. It would now be annoying to have to rename variables.
>>
>> I.e., you want to use the same name for different versions of the same thing, similarly to how you do not have to change the name of a variable when assigning to it.
> 
> Interesting that you bring that up. I've been slowly leaning towards the "single assignment" style, where a variable is only assigned to once, when it is initialized. Sort of a "head const" thing. Some languages enforce this (can't remember which ones).
> 
> I find it makes code more readable.
> ...

My example in fact follows that style. Each variable is a separate variable that is initialized once.

> I get the feeling that allowing not only the contents, but the type of the variable change after re-assignment makes for less coherent code.
> ...

This is not being proposed. Redeclaration after move is not the same as reassignment.

> I'm not sure why, but I find Rust code hard to read. Maybe that's part of it.

Plenty of unfamiliar things in Rust to throw you off.

> I like the O/B system so much I implemented it in D (@live), not only that, but have begun adopting it as my own coding style. And it looks sooo much nicer in D syntax!
> ...

@live, while sharing some concepts, is ultimately not even the same kind of thing.

> 
>>> We already disallow shadowing declarations, and that has prevented a number of bugs at least in my own code (they're very difficult to spot with a visual check).
>>
>> The reason why shadowing is error prone is that multiple variables with overlapping lifetimes are in scope and the compiler arbitrarily picks one of them. This case is different, as only one variable of the same name exists at any given time. This is not error prone.
> 
> I've made that error myself now and then, usually as a result of moving code lines about.
> ...

Well, you can always make an error by moving some code lines into a scope in which they do not fit, but in this case, if you accidentally redeclare a variable of a name that already exists, you get a compile-time error.

>> Requiring unique names is more error prone in this case, as you can accidentally copy an older version of a variable.
> 
> I can't remember making that error :-/
> ...

Presumably you used in-place updates in cases where it would have been hard to keep track of different versions.

> 
>> Anyway, this is not the most important thing, please do check out the points I initially included in my review. This point is just something I had forgotten to include.
> 
> Of course. This was just an easy to respond to issue.
> ...

I do feel like the response was shot from the hip.

> But a caveat. I'm kinda swamped at the moment. I'm working on some cool stuff for the upcoming DConf.

Of course! :)

> I also wrote the Move/Copy/Forward DIP before I worked on the O/B system for D. The whole Move/Copy/Forward needs to be studied in the context of how it fits in with O/B. This is going to need some careful study.

Sure. (This was also one of the things I pointed out.)
February 29

On Thursday, 29 February 2024 at 04:17:38 UTC, Walter Bright wrote:

>

DFA works with mathematical reliability (absent compiler bugs). The optimizer relies heavily on DFA; if DFA was unreliable the whole edifice will fall apart. Leave move vs copy to the compiler. The move vs copy/destroy choice is an optimization, and should be semantically thought of it that way.

I'm a bit worried this would lead to implementation-defined behaviour on whether uses of a struct with a disabled copy constructor would compile.

But otherwise I agree with this. Well, the spec currently doesn't allow these elision in all cases, for example it says:

struct A
{
    this(ref return scope A another) {}
}

void fun(A a) {}

void main()
{
    A a;
    fun(a);    // copy constructor gets called
}

...but I tend to agree it should be implementation-defined whether the copy constructor is called here.

February 29
On Thursday, February 29, 2024 3:10:30 AM MST Atila Neves via Digitalmars-d wrote:
> On Thursday, 29 February 2024 at 04:17:38 UTC, Walter Bright
>
> wrote:
> > On 2/28/2024 1:03 PM, Timon Gehr wrote:
> >> No, the idea is that the compiler enforces that it is indeed the last use and produces a compile-time error message if it cannot prove that it is the case.
> >
> > DFA works with mathematical reliability (absent compiler bugs). The optimizer relies heavily on DFA; if DFA was unreliable the whole edifice will fall apart. Leave move vs copy to the compiler. The move vs copy/destroy choice is an optimization, and should be semantically thought of it that way.
>
> Not always. Some types have to be move-only.

Yeah. One of the proposed ways to handle basic input ranges with Phobos v3 is to make them be move-only / non-copyable (and really, it's either that or force them all to be reference types, both of which come with their own issues). And if we do decide to make basic input ranges non-copyable, then explicit moves are very much going to be a thing with them, and the more type-safe we can make that the better. Being able to have the compiler do automatic moves on last use would also be huge for that, since it would drastically reduce the number of explicit moves which would be required, but there would still be times when it would have to be explicit.

Also, I'm pretty sure that the folks who have been the most motivated with regards to move constructors and move semantics (e.g. the folks at Weka) have wanted it explicitly because of issues surrounding non-copyable types. So, while having moves as an optimization is important (and can be big for both performance in general and for usability with non-copyable types), I don't think that that's actually the primary motivator behind the DIP in question - at least not with regards to the folks who were pushing for it to be created in the first place.

- Jonathan M Davis



March 01
On Thursday, 29 February 2024 at 05:51:32 UTC, electricface wrote:
> On Thursday, 29 February 2024 at 04:24:02 UTC, Walter Bright wrote:
>> I do share your concern, Don. I worry that we know so well how C++ does it that we can't conceive of a better way. If we cannot do mucho better than C++ with this, why are we using D?
>>
>> D already does some things so much better than C++ by thinking "dammit there's got to be a better way than this mess!"
>>
>> Things like no ADL, decent modules, simple template syntax, easy to understand overloading, context-free grammar, slices, etc.
>
> Try using DFA technology not during normal compilation in the compiler, but in an alternative checking mode that maintains compilation performance while allowing program-assisted analysis.

Try to linguistically mark the last usage of a variable, and then develop a fast verification algorithm to improve the performance of analyzing the last usage of variables. It can also use expensive but comprehensive algorithm to guide people in marking the last usage of a variable.
March 01
On Thursday, 29 February 2024 at 04:24:02 UTC, Walter Bright wrote:
> I do share your concern, Don. I worry that we know so well how C++ does it that we can't conceive of a better way. If we cannot do mucho better than C++ with this, why are we using D?
>
> D already does some things so much better than C++ by thinking "dammit there's got to be a better way than this mess!"
>
> Things like no ADL, decent modules, simple template syntax, easy to understand overloading, context-free grammar, slices, etc.

I've expressed sentiments like this before, Walter, most recently at the time of the fork kerfuffle. I really value your careful, let's-think-it-through engineer's approach regarding the evolution of D. I'm not suggesting that the young-uns are always wrong. Quite the contrary; there are some very bright young people pushing D in various ways. The problem is that they aren't always right, which creates a wheat-from-the-chaff role for the project leader, sometimes requiring rejecting changes backed by many and other times implementing things that lack enthusiastic support, e.g, ImportC, which I hope people are beginning to understand that it is a major asset for D. I think you are doing a superb job of walking this fine line.


1 2 3 4
Next ›   Last »