www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - If you could make any changes to D, what would they look like?

reply SealabJaster <sealabjaster gmail.com> writes:
Just for giggles, without pesky things like breaking changes; 
rational thinking, logical reasoning behind the changes, etc.

What interesting changes would you make to the language, and what 
could they possibly look like?

Here's a small example of some things I'd like.

```d
import std;

interface Animal
{
     void speak(string language);
}

struct Dog
{
      nogc  nothrow  pure  safe
     static void speak(string l)
     {
         // Pattern matching of some kind
         // With strings this is just a fancy switch statement, 
but this is the gist of it
         match l with
         {
             "english" => writeln("woof"),
             "french" => writeln("le woof"),
             _ => writeln("foow")
         }
     }
}

struct Cat
{
     // Remove historical baggage. Make old attributes into ` ` 
attributes
      nogc  nothrow  pure  safe
     static void speak()
     {
         writeln("meow")
     }
}

// ? for "explicitly nullable"
void doSpeak(alias T)(string? language)
if(is(T == struct) && match(T : Animal)) // Match structs against 
an interface.
{

     auto lang = language ?? "UNKNOWN";

     // So of course we'd need a mutable keyword of some sort
     mutable output = $"{__traits(identifier, T)} speaking in 
{lang}"; // String interpolation
     writeln(output);
     T.speak(lang);
}

void main()
{
     doSpeak!Dog;
     doSpeak!Cat; // Should be a compiler error since it fails the 
`match` statement
}
```
Oct 20 2021
next sibling parent reply IGotD- <nise nise.com> writes:
On Wednesday, 20 October 2021 at 09:47:54 UTC, SealabJaster wrote:
 ```
Priority 1 would introducing managed pointers (aka reference type, fat pointers) in order to fix the no 1 scare of them all, the GC.
Oct 20 2021
next sibling parent reply SealabJaster <sealabjaster gmail.com> writes:
On Wednesday, 20 October 2021 at 10:06:30 UTC, IGotD- wrote:
 Priority 1 would introducing managed pointers (aka reference 
 type, fat pointers) in order to fix the no 1 scare of them all, 
 the GC.
So something like `int^` to denote a GC pointer, and `int*` for a raw pointer?
Oct 20 2021
parent reply IGotD- <nise nise.com> writes:
On Wednesday, 20 October 2021 at 10:23:27 UTC, SealabJaster wrote:
 So something like `int^` to denote a GC pointer, and `int*` for 
 a raw pointer?
Something like that, which is exactly the syntax of "Managed C++".
Oct 20 2021
next sibling parent bauss <jj_1337 live.dk> writes:
On Wednesday, 20 October 2021 at 10:26:52 UTC, IGotD- wrote:
 On Wednesday, 20 October 2021 at 10:23:27 UTC, SealabJaster 
 wrote:
 So something like `int^` to denote a GC pointer, and `int*` 
 for a raw pointer?
Something like that, which is exactly the syntax of "Managed C++".
I'm not gonna lie, Managed C++ was actually nice
Oct 20 2021
prev sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
On Wednesday, 20 October 2021 at 10:26:52 UTC, IGotD- wrote:
 On Wednesday, 20 October 2021 at 10:23:27 UTC, SealabJaster 
 wrote:
 So something like `int^` to denote a GC pointer, and `int*` 
 for a raw pointer?
Something like that, which is exactly the syntax of "Managed C++".
What is the benefit of different pointer types? If you have a function that takes a pointer but doesn't make any allocations or anything, then you would need to have two versions of it to work with each type. This introduces a similar issue as what led to inout. It might make some kind of generics with type erasure that can handle all of these cases more useful.
Oct 20 2021
parent reply IGotD- <nise nise.com> writes:
On Wednesday, 20 October 2021 at 17:27:45 UTC, jmh530 wrote:
 What is the benefit of different pointer types?

 If you have a function that takes a pointer but doesn't make 
 any allocations or anything, then you would need to have two 
 versions of it to work with each type. This introduces a 
 similar issue as what led to inout. It might make some kind of 
 generics with type erasure that can handle all of these cases 
 more useful.
Correct, that is the drawback with different pointer types, that you might need two implementations, one for pointers and one for reference types. Normally generics takes care of that pretty well. The benefit is that references/fat-pointers enables operations under the hood that raw pointers don't allow. For example increasing/decreasing a reference count. If everything in Phobos/Druntime would use references instead of raw pointers where applicable, the a recompile would have enabled a complete change of the GC algorithm. Reference types enables more versatility.
Oct 20 2021
next sibling parent IGotD- <nise nise.com> writes:
On Wednesday, 20 October 2021 at 17:55:05 UTC, IGotD- wrote:
 Correct, that is the drawback with different pointer types, 
 that you might need two implementations, one for pointers and 
 one for reference types. Normally generics takes care of that 
 pretty well.

 The benefit is that references/fat-pointers enables operations 
 under the hood that raw pointers don't allow. For example 
 increasing/decreasing a reference count. If everything in 
 Phobos/Druntime would use references instead of raw pointers 
 where applicable, the a recompile would have enabled a complete 
 change of the GC algorithm. Reference types enables more 
 versatility.
The more I think of it, this solution is becoming more and more attractive. https://forum.dlang.org/post/bsmgpfdlylvpuojpkdgz forum.dlang.org I know that a reference type will never make it into D. However, the solution proposed offers an escape hatch without changing the core language. Managed memory is the norm and unmanaged memory will likely be used sparsely in most programs. There will be an unnecessary performance hit when using unmanaged memory (which is still managed but operations ignored at runtime) but it can be overridden (for example parameter passing) by a keyword for those very optimized situations which is a non breaking change.
Oct 20 2021
prev sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
On Wednesday, 20 October 2021 at 17:55:05 UTC, IGotD- wrote:
 [snip]
Correct, that is the drawback with different pointer types, that you might need two implementations, one for pointers and one for reference types. Normally generics takes care of that pretty well. The benefit is that references/fat-pointers enables operations under the hood that raw pointers don't allow. For example increasing/decreasing a reference count. If everything in Phobos/Druntime would use references instead of raw pointers where applicable, the a recompile would have enabled a complete change of the GC algorithm. Reference types enables more versatility.
My limited knowledge of Managed C++ is that it only allowed the fat pointers with the GC. Would it be possible to have it customizable so that for instance you could have T^ work with a garbage collector or switch it out for reference counting?
Oct 20 2021
parent IGotD- <nise nise.com> writes:
On Wednesday, 20 October 2021 at 18:19:55 UTC, jmh530 wrote:
 My limited knowledge of Managed C++ is that it only allowed the 
 fat pointers with the GC. Would it be possible to have it 
 customizable so that for instance you could have T^ work with a 
 garbage collector or switch it out for reference counting?
Yes, the key is to have a distinctive type for managed pointers. When you have that you can implement that pointer as you want. Nim has a distinctive type for managed memory and can change GC type by recompiling.
Oct 20 2021
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Oct 20, 2021 at 10:06:30AM +0000, IGotD- via Digitalmars-d wrote:
[...]
 Priority 1 would introducing managed pointers (aka reference type, fat
 pointers) in order to fix the no 1 scare of them all, the GC.
IMNSHO, priority 1 would be to kill GC-phobia with fire. ;-) Everything else follows naturally after that. T -- No! I'm not in denial!
Oct 20 2021
prev sibling next sibling parent reply Elronnd <elronnd elronnd.net> writes:
- Limited implicit conversions (adam has spoken about this before)

- First class support for non-nullable pointers and explicitly 
optional pointers

- Class instances should not be reference types

- Macros (cf core.reflect/codegen)

- || and && should not coerce to boolean (x || y is x ? x : y 
(but without double eval); x && y is x ? y : typeof(y).init)
Oct 20 2021
parent reply SealabJaster <sealabjaster gmail.com> writes:
On Wednesday, 20 October 2021 at 10:07:40 UTC, Elronnd wrote:
 - First class support for non-nullable pointers and explicitly 
 optional pointers
We can only dream.
 - Class instances should not be reference types
Interesting opinion. So classes would essentially be structs with a v-table (similar to C++ I believe)? From a quick think about how things would work, it seems like it's simplify generic code littered with `static if(is(T == class))`.
 - || and && should not coerce to boolean (x || y is x ? x : y 
 (but without double eval); x && y is x ? y : typeof(y).init)
I like that. It's similar to the likes of Lua where you can go `var = var or {}`
Oct 20 2021
parent reply Adam D Ruppe <destructionator gmail.com> writes:
On Wednesday, 20 October 2021 at 10:28:35 UTC, SealabJaster wrote:
 On Wednesday, 20 October 2021 at 10:07:40 UTC, Elronnd wrote:
 - Class instances should not be reference types
Interesting opinion. So classes would essentially be structs with a v-table
Well, one idea I heard last week that I don't loathe and despise is that classes are always *explicit* references. class Object {} void foo(Object* o){} That'd be the new syntax for what we have today. But: void foo(Object o) {} // error: cannot declare variable of class value type; the rule is still they're always references One of the benefits of this is there's now a place for rebindable: void foo(const(Object)* o) {} // const object, but you can rebind the reference That const ref thing has come up for about as long as D has had const. I don't really think it is worth the hassle but it is interesting in that it solves some old syntax worries.
Oct 20 2021
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Oct 20, 2021 at 11:41:32AM +0000, Adam D Ruppe via Digitalmars-d wrote:
[...]
 Well, one idea I heard last week that I don't loathe and despise is
 that classes are always *explicit* references.
 
 class Object {}
 
 void foo(Object* o){}
 
 That'd be the new syntax for what we have today. But:
 
 void foo(Object o) {} // error: cannot declare variable of class value
 type; the rule is still they're always references
 
 
 One of the benefits of this is there's now a place for rebindable:
 
 
 void foo(const(Object)* o) {} // const object, but you can rebind the
 reference
[...] Now *that* is a cool idea that might actually be worth doing. This would also make it easier to refactor programs switching between class/struct: switching from class to struct will retain reference semantics without any trouble, and switching from struct to class will automatically catch all the places that need attention because of potential change in semantics. T -- Some ideas are so stupid that only intellectuals could believe them. -- George Orwell
Oct 20 2021
prev sibling next sibling parent russhy <russhy gmail.com> writes:
On Wednesday, 20 October 2021 at 11:41:32 UTC, Adam D Ruppe wrote:

 I don't really think it is worth the hassle but it is 
 interesting in that it solves some old syntax worries.
It totally worth it, it part of the reason why i avoid classes, even thought i could malloc them.. i just don't want to introduce the concept of ``MyObject`` pointers without the star ``*`` It will introduce too much bugs and constantly having to check signature of parameters IS A PAIN that needs to go away, the sooner, the better
Oct 20 2021
prev sibling next sibling parent reply IGotD- <nise nise.com> writes:
On Wednesday, 20 October 2021 at 11:41:32 UTC, Adam D Ruppe wrote:
 Well, one idea I heard last week that I don't loathe and 
 despise is that classes are always *explicit* references.

 class Object {}

 void foo(Object* o){}

 That'd be the new syntax for what we have today. But:

 void foo(Object o) {} // error: cannot declare variable of 
 class value type; the rule is still they're always references


 One of the benefits of this is there's now a place for 
 rebindable:


 void foo(const(Object)* o) {} // const object, but you can 
 rebind the reference



 That const ref thing has come up for about as long as D has had 
 const.


 I don't really think it is worth the hassle but it is 
 interesting in that it solves some old syntax worries.
Just a small question, if you want a raw pointer to your object how would that look like. Since Object* is a reference (with things going on behind the scene), what is the raw pointer?
Oct 20 2021
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Oct 20, 2021 at 03:43:56PM +0000, IGotD- via Digitalmars-d wrote:
[...]
 Just a small question, if you want a raw pointer to your object how
 would that look like. Since Object* is a reference (with things going
 on behind the scene), what is the raw pointer?
Object* *is* the pointer. There's nothing going on behind the scenes. Or, in present notation, cast(void*)Object gives you the pointer. T -- Study gravitation, it's a field with a lot of potential.
Oct 20 2021
parent reply Adam D Ruppe <destructionator gmail.com> writes:
On Wednesday, 20 October 2021 at 15:53:53 UTC, H. S. Teoh wrote:
 Object* *is* the pointer.  There's nothing going on behind the 
 scenes.
well tho does obj++; move the pointer or call the operator on the class? I don't think this syntax is ideal (I kinda prefer the old `Object ref` proposal), but just it is something interesting to think about.
Oct 20 2021
next sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 10/20/21 11:58 AM, Adam D Ruppe wrote:
 On Wednesday, 20 October 2021 at 15:53:53 UTC, H. S. Teoh wrote:
 Object* *is* the pointer.  There's nothing going on behind the scenes.
well tho does obj++; move the pointer or call the operator on the class?
Yeah, this proposal falls apart there. I don't think it's viable. If obj++ doesn't mean go to the next Object, then you are trading one WTF for another.
 I don't think this syntax is ideal (I kinda prefer the old `Object ref` 
 proposal), but just it is something interesting to think about.
I actually wrote an unpublished blog article (from 2013) arguing for something to fix this (it fixes a lot of other things too). But I received such negative feedback, I abandoned it. -Steve
Oct 20 2021
prev sibling next sibling parent Elronnd <elronnd elronnd.net> writes:
On Wednesday, 20 October 2021 at 15:58:49 UTC, Adam D Ruppe wrote:
 On Wednesday, 20 October 2021 at 15:53:53 UTC, H. S. Teoh wrote:
 Object* *is* the pointer.  There's nothing going on behind the 
 scenes.
well tho does obj++; move the pointer or call the operator on the class? I don't think this syntax is ideal (I kinda prefer the old `Object ref` proposal), but just it is something interesting to think about.
FWIW the 'non-nullable & explicitly nullable pointers' proposal resolves this. Because the point of such pointers is that they always point at exactly one object (or at nothing), there is no point in indexing or adding anything to them etc., so such overloaded operators can be forwarded. (As x.y -> (*x).y, so x.opUnary!"++" -> (*x).upUnary!"++".) However I just realised unary * can be overloaded. Obviously *x should be a dereference. But as that is a fairly fringe use case (and the point of the exercise was to ignore compatibility anyway), I think it can be safely steamrolled.
Oct 20 2021
prev sibling parent Tejas <notrealemail gmail.com> writes:
On Wednesday, 20 October 2021 at 15:58:49 UTC, Adam D Ruppe wrote:
 On Wednesday, 20 October 2021 at 15:53:53 UTC, H. S. Teoh wrote:
 Object* *is* the pointer.  There's nothing going on behind the 
 scenes.
well tho does obj++; move the pointer or call the operator on the class? I don't think this syntax is ideal (I kinda prefer the old `Object ref` proposal), but just it is something interesting to think about.
Umm, do we have to do anything in this case? It'll be the same as for structs: ```d import std; struct S{ void opUnary(string s : "++")(){ writeln(`called opUnary!("++")()`); } int a; } void func(S* param){ writeln((*param).a); (*param)++; } void main() { S s; func(&s); } ``` You use `ref`, you can't do pointer arithmetic anymore.
Oct 20 2021
prev sibling parent reply Guillaume Piolat <first.last gmail.com> writes:
On Wednesday, 20 October 2021 at 11:41:32 UTC, Adam D Ruppe wrote:
 I don't really think it is worth the hassle but it is 
 interesting in that it solves some old syntax worries.
One of the problem C++ has with objects-with-inheritance-as-value-type is "object slicing". It is ugly in theory: https://stackoverflow.com/a/14461532 I guess it's one of the reason D doesn't have that, so a discussion about Object* needs to address that issue.
Oct 20 2021
next sibling parent Adam D Ruppe <destructionator gmail.com> writes:
On Wednesday, 20 October 2021 at 17:28:30 UTC, Guillaume Piolat 
wrote:
 One of the problem C++ has with 
 objects-with-inheritance-as-value-type is "object slicing".
That's why: void foo(Object o) {} // error: cannot declare variable of class value type; the rule is still they're always references It still requires you to always use it by reference, you just have to write it out differently.
Oct 20 2021
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Oct 20, 2021 at 05:28:30PM +0000, Guillaume Piolat via Digitalmars-d
wrote:
[...]
 One of the problem C++ has with objects-with-inheritance-as-value-type
 is "object slicing".
 
 It is ugly in theory: https://stackoverflow.com/a/14461532
 
 I guess it's one of the reason D doesn't have that, so a discussion
 about Object* needs to address that issue.
Yes, I believe the original reason D treats classes as inherently by-reference is precisely because of this. Consider: class A { int x; virtual int getValue() { return x; } } class B : A { int y; override int getValue() { return y; } } B b; A a = b; // allowed since B is a subtype of A. writeln(a.getValue()); // uh oh Assume by-value semantics for the sake of argument. So `a` gets a copy of part of B, because there is only enough space allocated on the stack to store an A but not a B. So `y` is missing. Then what should a.getValue return? Since it is virtual, it should call B.getValue, which accesses a non-existent member, or in more practical terms, it reads from the stack outside of the bounds of A. All sorts of subtle (and nasty) problems crop up when you mix polymorphic class objects with by-value semantics. That's why D's design makes more sense: if you want OO-style inheritance, let it be by-reference all the way. If you want by-value semantics, which is incompatible with OO-style polymorphism, use a struct. Simple solution, avoids all the nastiness you have to deal with in C++. T -- Never step over a puddle, always step around it. Chances are that whatever made it is still dripping.
Oct 20 2021
prev sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Wednesday, 20 October 2021 at 17:28:30 UTC, Guillaume Piolat 
wrote:
 On Wednesday, 20 October 2021 at 11:41:32 UTC, Adam D Ruppe 
 wrote:
 I don't really think it is worth the hassle but it is 
 interesting in that it solves some old syntax worries.
One of the problem C++ has with objects-with-inheritance-as-value-type is "object slicing". It is ugly in theory: https://stackoverflow.com/a/14461532
Never had any issues with this in real life, this is one of many things you should think about when modelling. The real "problem" with C++ is that it both provides a lot of flexibility-for-responsible-designers, but also provides defaults. Since people don't write the default it is easier to forget to adjust the semantics of all member functions to the model.
Oct 20 2021
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 10/20/21 11:02 AM, Ola Fosheim Gr=C3=B8stad wrote:
 On Wednesday, 20 October 2021 at 17:28:30 UTC, Guillaume Piolat wrote:=
 One of the problem C++ has with objects-with-inheritance-as-value-typ=
e
 is "object slicing".

 It is ugly in theory: https://stackoverflow.com/a/14461532
Never had any issues with this in real life,
Me neither because I was following convention in C++: "Pass inherited=20 types by reference because there is slicing." That rule renders C++=20 classes (and structs) of inheritance hierarchies "reference types". I=20 Ali
Oct 20 2021
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Wednesday, 20 October 2021 at 21:05:44 UTC, Ali Çehreli wrote:
 Me neither because I was following convention in C++: "Pass 
 inherited types by reference because there is slicing." That 
 rule renders C++ classes (and structs) of inheritance 

 approach here.
That is the classical Simula model. Simula did however use ":-" for reference assigment and ":=" for value assignment, so it was always clear to the user that objects followed reference semantics.
Oct 21 2021
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Oct 20, 2021 at 09:47:54AM +0000, SealabJaster via Digitalmars-d wrote:
 Just for giggles, without pesky things like breaking changes; rational
 thinking, logical reasoning behind the changes, etc.
 
 What interesting changes would you make to the language, and what
 could they possibly look like?
1) Kill current int promotion rules with fire. 2) Kill autodecoding with the same. 3) Kill the current class hierarchy, make ProtoObject -> Object. 4) The compiler would infer which function parameters need to be runtime and which can be completely resolved at compile-time. You wouldn't need to separate the two; it would be inferred for you. Well, OK, in some cases this is not possible, then you have to annotate it. But otherwise, it should be automatic. Template functions and "ordinary" functions would be one and the same. The compiler figures out for you which parameters should be compile-time and which should be runtime. Flipping a switch would toggle between optimizing for performance (maximize compile-time binding) vs size (minimize compile-time binding, genericize parameters to accept polymorphic runtime types). 5) Compiler would auto-rewrite your source code to add/update function annotations so that you never have to actually write them yourself. This includes annotations that only the compiler can figure out, like flow analysis information, code invariants, etc., stuff that would solve a bunch of problems with separate compilation not having full information about a function. Also, the compiler will auto-fix typos for you (and update the source code) if it makes the code compilable. 6) Completely pay-as-you-go std library / codegen, i.e., if your program consists of `void main() {}` it should weigh less than 1KB. (In fact, it should weigh 45 bytes.[1] :-D) If you writeln("hello world") it should not pull in code for formatting floats. Executables would contain the absolute bare minimum to do what it's supposed to do, not a single byte more. 7) In-contracts should run in the caller, not the callee. 8) There should be a new standard library function called dwimnwis,[2] that uses neural networks to predict what the programmer meant rather than what he actually wrote, and does that instead of what the source code says. [1] http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html [2] Do What I Mean, Not What I Said. T -- I am Ohm of Borg. Resistance is voltage over current.
Oct 20 2021
prev sibling next sibling parent Basile B. <b2.temp gmx.com> writes:
On Wednesday, 20 October 2021 at 09:47:54 UTC, SealabJaster wrote:
 Just for giggles, without pesky things like breaking changes; 
 rational thinking, logical reasoning behind the changes, etc.

 What interesting changes would you make to the language, and 
 what could they possibly look like?
Optional access operator. I tend to write more and more conditional expression with `null` as the third operand. For example I count 93 occurences of the pattern in styx. Not convinced that this is a very common construct ? Let's take a look at dmd: **490** occurences. Example picked from dmd: ```d StringExp se = ex ? ex.ctfeInterpret().toStringExp() `: null`;" ``` becomes ```d StringExp se = ex?.ctfeInterpret().toStringExp(); ``` Please abstraint you to reply to me with remarks like _"this can be done using metaprog"_ ... I know because I did it too a few years ago (using `opDispatch`). This should be built in the language because 1. the template solution is necessarily slow to compile 2. it is better for tooling
Oct 20 2021
prev sibling next sibling parent reply kyle <kyle kyle.kyle> writes:
On Wednesday, 20 October 2021 at 09:47:54 UTC, SealabJaster wrote:
 If you could make any changes to D, what would they look like?
I want some non-ridiculous way to use +=, -=, *=, etc with property functions. Whether that's with property or otherwise. And I want https://issues.dlang.org/show_bug.cgi?id=21321 (Class with unimplemented interface method compiles, links, then segfaults, if inherited through abstract base class) fixed.
Oct 20 2021
parent reply 12345swordy <alexanderheistermann gmail.com> writes:
On Wednesday, 20 October 2021 at 18:45:42 UTC, kyle wrote:
 On Wednesday, 20 October 2021 at 09:47:54 UTC, SealabJaster 
 wrote:
 If you could make any changes to D, what would they look like?
I want some non-ridiculous way to use +=, -=, *=, etc with property functions. Whether that's with property or otherwise. And I want https://issues.dlang.org/show_bug.cgi?id=21321 (Class with unimplemented interface method compiles, links, then segfaults, if inherited through abstract base class) fixed.
I have a PR request that address this, if you want to check it out. -Alex
Oct 21 2021
parent kyle <kyle kyle.kyle> writes:
On Thursday, 21 October 2021 at 14:33:02 UTC, 12345swordy wrote:
 On Wednesday, 20 October 2021 at 18:45:42 UTC, kyle wrote:
 On Wednesday, 20 October 2021 at 09:47:54 UTC, SealabJaster 
 wrote:
 If you could make any changes to D, what would they look like?
I want some non-ridiculous way to use +=, -=, *=, etc with property functions. Whether that's with property or otherwise. And I want https://issues.dlang.org/show_bug.cgi?id=21321 (Class with unimplemented interface method compiles, links, then segfaults, if inherited through abstract base class) fixed.
I have a PR request that address this, if you want to check it out. -Alex
Looks like it would be a great quality of life change along with I'm curious exactly what is meant by "Disallow parameters for property functions". Does that mean the only allowed parameter is and these 2 PRs would bring D much closer there. Thanks for your work.
Oct 21 2021
prev sibling next sibling parent reply Kagamin <spam here.lot> writes:
Like many people here I tried to design my own language, but I 
noticed that I can't get (logically) puristic concepts into an 
adequate form. I grew suspicious it's because the problem domain 
itself is impure, so puristic concepts can't be adequate, and an 
adequate language should elegantly incorporate impurity instead 
of evading it at all wasteful costs. I also take this approach in 
program design, somehow a line is easy to see that the component 
shouldn't be more pure than this.
Oct 20 2021
next sibling parent SealabJaster <sealabjaster gmail.com> writes:
On Wednesday, 20 October 2021 at 19:41:52 UTC, Kagamin wrote:
 Like many people here I tried to design my own language, but I 
 noticed that I can't get (logically) puristic concepts into an 
 adequate form. I grew suspicious it's because the problem 
 domain itself is impure, so puristic concepts can't be 
 adequate, and an adequate language should elegantly incorporate 
 impurity instead of evading it at all wasteful costs. I also 
 take this approach in program design, somehow a line is easy to 
 see that the component shouldn't be more pure than this.
I've tried as well, but lack of experience and lack of attention span has made this hard to achieve. I had a crazy plan at one point to make my own assembler, then make my own language on top of that assembler, for whatever dumb reason. The main point of this post was to explore what D could've possibly looked like, based on the ideas of the forum dwellers. Here's a **very** rough estimation of other people's proposals: ```d class A {} class B : A {} // safe, pure, nothrow, etc all applied automatically by the compiler // Very small executable because we don't import phobos // Slightly big executable because we use the runtime (GC) void func(const(A)* a) {} // Adam's proposal void funcb(const(B)* b) { func(b); // I assume upcasting still works } void intPromotion() { byte b; b = 2 + 3; // byte b = b << 1; // byte } int* pointers() { int^ managed = new int; // Explicit nullable int*? raw = cast(int*?)malloc(int.sizeof); // ! == "Definitely not null" // Using the proposed || == x ? x : y syntax return raw! || managed.ptr; } void nullChaining(SomeClass*? value) { value?.subvalue?.func(); } ```
Oct 20 2021
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Oct 20, 2021 at 07:41:52PM +0000, Kagamin via Digitalmars-d wrote:
 Like many people here I tried to design my own language, but I noticed
 that I can't get (logically) puristic concepts into an adequate form.
 I grew suspicious it's because the problem domain itself is impure, so
 puristic concepts can't be adequate, and an adequate language should
 elegantly incorporate impurity instead of evading it at all wasteful
 costs. I also take this approach in program design, somehow a line is
 easy to see that the component shouldn't be more pure than this.
This is why I like D's approach to (functional) purity: it's defined in terms of its effect on the outside world, rather than internal purity (in the sense of a real functional language like Haskell). As Andrei once put it, you can have all the imperative, impure, dirty laundry you want inside your function body, but as long as the outside world can't see it, you're effectively a pure function to them. Also, one of D's stated goals is to be a systems programming language, in the sense of being able to implement the GC within the language itself instead of resorting to some other language required to perform the unsafe operations required to implement a GC. I.e., it's still D under the hood, not some other language. For this, there must be escape hatches from things like safe or immutable, since the GC must be able to manipulate untyped memory without running afoul of immutability (e.g., it must be able to cast mutable memory from its heap into an immutable object during allocation, and vice versa during collection). To user code, the memory is truly immutable, but the GC is actually performing low-level casts to create it under the hood. IOW, things like purity, immutability, etc., apply to the outward-facing APIs, but the implementation need not be thus constrained as long as it continues to fulfill the promises of attributes of the API. T -- Too many people have open minds but closed eyes.
Oct 20 2021
prev sibling next sibling parent reply Sebastiaan Koppe <mail skoppe.eu> writes:
On Wednesday, 20 October 2021 at 09:47:54 UTC, SealabJaster wrote:
 Just for giggles, without pesky things like breaking changes; 
 rational thinking, logical reasoning behind the changes, etc.

 What interesting changes would you make to the language, and 
 what could they possibly look like?
Unique/Isolated; A way to declare or require that an object (both struct and class) cannot have more than one reference. For instance, it is perfectly fine to move an AA across thread boundaries if there is only one reference to it. But in the general you can't because you can't proof there is. This touches on live a bit, but live is put on functions whereas I want it on objects (class+structs).
Oct 21 2021
parent Elronnd <elronnd elronnd.net> writes:
On Thursday, 21 October 2021 at 20:26:47 UTC, Sebastiaan Koppe 
wrote:
 Unique/Isolated; A way to declare or require that an object 
 (both struct and class) cannot have more than one reference.

 For instance, it is perfectly fine to move an AA across thread 
 boundaries if there is only one reference to it. But in the 
 general you can't because you can't proof there is.

 This touches on  live a bit, but  live is put on functions 
 whereas I want it on objects (class+structs).
It's the difference between linear types and uniqueness types. 'Linear' is part of the api contract of the callee: it promises not to change the number of references to an object. While 'unique' is part of the api contract of the caller: it promises an object to which there is only one object.
Oct 21 2021
prev sibling next sibling parent reply Gavin Ray <gavinray example.com> writes:
On Wednesday, 20 October 2021 at 09:47:54 UTC, SealabJaster wrote:
 ```
I think the language is lovely -- my sole wish is that there was a bit more tooling integrated into the core of language. Mostly a Language Server. I cannot use UFCS at all because Code-D for VS Code isn't able to properly resolve them using the community Serve-D lang server. It's not the end of the world, I just nest the functions instead, but it sort of ruins the beauty of the language.
Oct 24 2021
next sibling parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Sunday, 24 October 2021 at 17:42:28 UTC, Gavin Ray wrote:
 On Wednesday, 20 October 2021 at 09:47:54 UTC, SealabJaster 
 wrote:
 ```
I think the language is lovely -- my sole wish is that there was a bit more tooling integrated into the core of language. Mostly a Language Server. I cannot use UFCS at all because Code-D for VS Code isn't able to properly resolve them using the community Serve-D lang server. It's not the end of the world, I just nest the functions instead, but it sort of ruins the beauty of the language.
Iirc there are some pr for this
Oct 24 2021
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 24 October 2021 at 17:42:28 UTC, Gavin Ray wrote:
 I cannot use UFCS at all because Code-D for VS Code isn't able 
 to properly resolve them using the community Serve-D lang 
 server.
Genuine question: is it really so difficult to write such code "by hand"? I understand that language servers are convenient, but I find it almost impossible to imagine being completely unwilling or unable to write code without one. I ask because I suspect most D users (including core contributors) regard this sort of tooling support as "nice to have," but not "essential", and therefore do not give a high priority to improving it. If it really is "essential" to a significant fraction of programmers, we might want to rethink that stance.
Oct 24 2021
next sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Sunday, 24 October 2021 at 19:00:17 UTC, Paul Backus wrote:
 On Sunday, 24 October 2021 at 17:42:28 UTC, Gavin Ray wrote:
 I cannot use UFCS at all because Code-D for VS Code isn't able 
 to properly resolve them using the community Serve-D lang 
 server.
Genuine question: is it really so difficult to write such code "by hand"? I understand that language servers are convenient, but I find it almost impossible to imagine being completely unwilling or unable to write code without one.
I believe it's partly a problem of lazyness, e.g "I want to type less. I want to be proposed `filter` when I CTRL+SPACE after `arr.fi` ". That case can be solved by doing a word split on the current document and by adding the results to the "good" completions when they are requested. If you have selective imports at the top of the module, for filter, map, each etc. then they are proposed as well. The other part of the problem is that completion can be used to overcome the lack of knowledge of an API, for example. In this case the word split does not help.
 I ask because I suspect most D users (including core 
 contributors) regard this sort of tooling support as "nice to 
 have," but not "essential", and therefore do not give a high 
 priority to improving it. If it really is "essential" to a 
 significant fraction of programmers, we might want to rethink 
 that stance.
Oct 24 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 24 October 2021 at 19:47:10 UTC, Basile B. wrote:
 I believe it's partly a problem of lazyness, e.g "I want to 
 type less. I want to be proposed `filter` when I CTRL+SPACE 
 after `arr.fi` ".

 That case can be solved by doing a word split on the current 
 document and by adding the results to the "good" completions 
 when they are requested. If you have selective imports at the 
 top of the module, for filter, map, each etc. then they are 
 proposed as well.
Yeah, this is basically how Vim's built-in completion works. I sometimes use it to save typing on long identifiers, or to avoid spelling mistakes. But if I have to fall back to typing something out manually, it's not that big of a deal. Maybe it's programmers who [can't touch-type][1] who rely heavily on tooling support? I know if I had to hunt-and-peck every letter of an identifier like `formattedWrite`, I'd be a lot more motivated to use code completion everywhere I could. [1]: https://steve-yegge.blogspot.com/2008/09/programmings-dirtiest-little-secret.html
 The other part of the problem is that completion can be used to 
 overcome the lack of knowledge of an API, for example. In this 
 case the word split does not help.
Yeah, in that situation having an easy way to view the docs is really helpful. I have a shortcut set up in Vim that opens the [dpldocs.info][2] search page with the identifier under the cursor, which works pretty well for the standard library and ok for dub packages, but a language server could probably do a better job. [2]: http://dpldocs.info/
Oct 24 2021
next sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Sunday, 24 October 2021 at 20:04:05 UTC, Paul Backus wrote:
 On Sunday, 24 October 2021 at 19:47:10 UTC, Basile B. wrote:
 Maybe it's programmers who [can't touch-type][1] who rely 
 heavily on tooling support? I know if I had to hunt-and-peck 
 every letter of an identifier like `formattedWrite`, I'd be a 
 lot more motivated to use code completion everywhere I could.
FWIW, I think it has less todo with being capable than being competitive. It is a lot easier to learn a new language or framework with high quality completion and precanned suggestions. This is becoming the standard for mature tooling, so it is only natural that more people will make it a prerequisite.
Oct 24 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 24 October 2021 at 22:33:51 UTC, Ola Fosheim Grøstad 
wrote:
 FWIW, I think it has less todo with being capable than being 
 competitive. It is a lot easier to learn a new language or 
 framework with high quality completion and precanned 
 suggestions. This is becoming the standard for mature tooling, 
 so it is only natural that more people will make it a 
 prerequisite.
That explains why someone accustomed to mature tooling might choose to avoid D entirely. I do not think it explains the behavior described in [this post by Gavin Ray][1], which is what I was responding to. [1]: https://forum.dlang.org/post/fblluqbuppomlokuosuh forum.dlang.org
Oct 24 2021
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Sunday, 24 October 2021 at 22:44:39 UTC, Paul Backus wrote:
 That explains why someone accustomed to mature tooling might 
 choose to avoid D entirely. I do not think it explains the 
 behavior described in [this post by Gavin Ray][1], which is 
 what I was responding to.
I think there is a shift in how people expect to learn, reducing cognitive load and providing easy navigation makes the process less frustrating.
Oct 24 2021
prev sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Sunday, 24 October 2021 at 20:04:05 UTC, Paul Backus wrote:
 On Sunday, 24 October 2021 at 19:47:10 UTC, Basile B. wrote:
 I believe it's partly a problem of lazyness, e.g "I want to 
 type less. I want to be proposed `filter` when I CTRL+SPACE 
 after `arr.fi` ".

 That case can be solved by doing a word split on the current 
 document and by adding the results to the "good" completions 
 when they are requested. If you have selective imports at the 
 top of the module, for filter, map, each etc. then they are 
 proposed as well.
Yeah, this is basically how Vim's built-in completion works. I sometimes use it to save typing on long identifiers, or to avoid spelling mistakes. But if I have to fall back to typing something out manually, it's not that big of a deal. Maybe it's programmers who [can't touch-type][1] who rely heavily on tooling support? I know if I had to hunt-and-peck every letter of an identifier like `formattedWrite`, I'd be a lot more motivated to use code completion everywhere I could. [1]: https://steve-yegge.blogspot.com/2008/09/programmings-dirtiest-little-secret.html
 The other part of the problem is that completion can be used 
 to overcome the lack of knowledge of an API, for example. In 
 this case the word split does not help.
Yeah, in that situation having an easy way to view the docs is really helpful. I have a shortcut set up in Vim that opens the [dpldocs.info][2] search page with the identifier under the cursor, which works pretty well for the standard library and ok for dub packages, but a language server could probably do a better job. [2]: http://dpldocs.info/
I'm thinking to a new design lately. The deamon would just keep the ast in sync and when a request is made the semantic is run from a particular point. The idea is that there's no need to run the sema for everything on each request. We only need to have the AST up to date, undecorate it when a file changes, redecorate it when request are made. And important, only redecorate from what's asked.
Oct 25 2021
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Oct 25, 2021 at 11:34:10PM +0000, Basile B. via Digitalmars-d wrote:
 On Sunday, 24 October 2021 at 20:04:05 UTC, Paul Backus wrote:
[...]
 I have a shortcut set up in Vim that opens the [dpldocs.info][2]
 search page with the identifier under the cursor, which works pretty
 well for the standard library and ok for dub packages, but a
 language server could probably do a better job.
 
 [2]: http://dpldocs.info/
I'm thinking to a new design lately. The deamon would just keep the ast in sync and when a request is made the semantic is run from a particular point. The idea is that there's no need to run the sema for everything on each request. We only need to have the AST up to date, undecorate it when a file changes, redecorate it when request are made. And important, only redecorate from what's asked.
The problem with this is that the current DMDFE mutates the AST as it goes along. So you either have to save a pristine copy of it somewhere in the server, somehow isolate and apply changes to it as you go along, and re-inject it into DMDFE (which according to Walter doesn't really improve performance that much), or you have to rewrite large swathes of the compiler to do its work without mutating the AST. T -- Never step over a puddle, always step around it. Chances are that whatever made it is still dripping.
Oct 25 2021
parent reply Basile B. <b2.temp gmx.com> writes:
On Monday, 25 October 2021 at 23:50:59 UTC, H. S. Teoh wrote:
 On Mon, Oct 25, 2021 at 11:34:10PM +0000, Basile B. via 
 Digitalmars-d wrote:
 On Sunday, 24 October 2021 at 20:04:05 UTC, Paul Backus wrote:
[...]
 I have a shortcut set up in Vim that opens the 
 [dpldocs.info][2] search page with the identifier under the 
 cursor, which works pretty well for the standard library and 
 ok for dub packages, but a language server could probably do 
 a better job.
 
 [2]: http://dpldocs.info/
I'm thinking to a new design lately. The deamon would just keep the ast in sync and when a request is made the semantic is run from a particular point. The idea is that there's no need to run the sema for everything on each request. We only need to have the AST up to date, undecorate it when a file changes, redecorate it when request are made. And important, only redecorate from what's asked.
The problem with this is that the current DMDFE mutates the AST as it goes along.
yeah indeed, aka lowerings...
Oct 25 2021
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Oct 26, 2021 at 01:01:04AM +0000, Basile B. via Digitalmars-d wrote:
 On Monday, 25 October 2021 at 23:50:59 UTC, H. S. Teoh wrote:
 On Mon, Oct 25, 2021 at 11:34:10PM +0000, Basile B. via Digitalmars-d
 wrote:
[...]
 We only need to have the AST up to date, undecorate it when a file
 changes, redecorate it when request are made. And important, only
 redecorate from what's asked.
The problem with this is that the current DMDFE mutates the AST as it goes along.
yeah indeed, aka lowerings...
But the thing is, lowerings don't *have* to be implemented as AST mutations. They can be a side-branch of the AST node for example. Or just translate to pure IR along with the rest of the code, then transformed as IR without touching the AST. It's more efficient (and less bug-prone) to transform IR than to meddle with the AST anyway. T -- Тише едешь, дальше будешь.
Oct 25 2021
prev sibling parent Gavin Ray <gavinray example.com> writes:
On Sunday, 24 October 2021 at 19:00:17 UTC, Paul Backus wrote:
 Genuine question: is it really so difficult to write such code 
 "by hand"? I understand that language servers are convenient, 
 but I find it almost impossible to imagine being completely 
 unwilling or unable to write code without one.
Oh no, I do not have much issue with writing things by hand. It's that using any post-fix/UFCS style function breaks the type-detection entirely. Here's a recording to explain what I mean: [Imgur Video - Dlang Tooling Issues](https://imgur.com/mf0KJk3) So you see here that: - The UCFS form of the same function used on a range gives no suggestions, documentations, etc - The regular form gives on-hover docs, I can ctrl+click to go to it's definition - Another problem which I didn't mention earlier: the state of D tooling allows you to pass the wrong types to functions. Here I pass `string[]` to a function which only takes `int[]` and the linter/lang server does not catch it. Only when I compile will a warning be showed. Here's another good visual example with `partition` function: - Trying to use UCFS to see which methods are available for my `Range` type, or to select the `partition` function I expect to see in the list to read the documentation ![](https://i.imgur.com/5DsEOoF.png) - Just using regular function calls ![](https://i.imgur.com/juEa1lt.png) ![](https://i.imgur.com/5Y3G0Nt.png)
Oct 24 2021
prev sibling next sibling parent reply Menshikov <mensikovk817 gmail.com> writes:
On Wednesday, 20 October 2021 at 09:47:54 UTC, SealabJaster wrote:
 Just for giggles, without pesky things like breaking changes; 
 rational thinking, logical reasoning behind the changes, etc.

 What interesting changes would you make to the language, and 
 what could they possibly look like?
 ```
```d //the type of the function depends on the type of the callback parameter void func(__depend void function() a){ a(); } void func1() nogc{ func({printf("Hello, World!\n");}); } void func2(){ func({writeln("Hello, World!");}); } ```
Oct 24 2021
parent Ben Jones <fake fake.fake> writes:
On Sunday, 24 October 2021 at 21:37:05 UTC, Menshikov wrote:
 On Wednesday, 20 October 2021 at 09:47:54 UTC, SealabJaster
How about just adding a compile time parameter to nogc? `void usesCallback(Func)(Func f) nogc(isNoGc!Func){ f(); }`
Oct 24 2021
prev sibling next sibling parent reply zjh <fqbqrr 163.com> writes:
On Wednesday, 20 October 2021 at 09:47:54 UTC, SealabJaster wrote:
 Just for giggles, without pesky things like breaking changes;
`C++` concept `constraints` are placed in front of types. I think it is elegant and worth learning. e.g. `fun(InputRange R,...)(...)`.
Oct 24 2021
parent zjh <fqbqrr 163.com> writes:
On Monday, 25 October 2021 at 02:17:13 UTC, zjh wrote:

Similarly, `...` is also very comfortable to use. There is no 
need to expand the cycle manually.
Oct 24 2021
prev sibling next sibling parent reply Guillaume Piolat <first.last gmail.com> writes:
 If you could make any changes to D, what would they look like?
It's a great thing other people get to decide, because I would remove: - pure - shared - property - inout - GC-calling-destructors - virtual-by-default - real - synchronized - inout - alias this - and parts of Phobos too. But D is also about catering to many different people.
Oct 25 2021
next sibling parent reply IGotD- <nise nise.com> writes:
On Monday, 25 October 2021 at 13:42:19 UTC, Guillaume Piolat 
wrote:
 It's a great thing other people get to decide, because I would 
 remove:
 - pure
 - shared
 -  property
 - inout
 - GC-calling-destructors
 - virtual-by-default
 - real
 - synchronized
 - inout
 - alias this
 - and parts of Phobos too.

 But D is also about catering to many different people.
Finally a post that made me wake up from my sleep. Interesting I agree with most of it.
 - pure
Agreed, if you don't want a function messing around with a global state, don't mess around with a global state, easy. You don't need a badge for that. I'm sure there are holes in the pure rule that the compiler cannot detect as well.
 - shared
Agreed, shared is one of the biggest mess in D. This is where it is obvious where the D language designers lack in experience and knowledge. It is also a very complex issue so my advice is to rather to copy an existing design or not at all and rely more on manual concurrent "safety".
 -  property
Do we need it? Isn't this partly handled by UFCS.
 - inout
Isn't this depreciated?
 - GC-calling-destructors
GC calling destructors is often not used. However, there might be rare occasions we need it. Like knowing when something is being destroyed.
 - virtual-by-default
Agreed, it should be final by default. This one of the rare occasions where the D maintainers agree.
 - real
Agreed, real can be removed. Even Intel doesn't optimize their old FPU anymore and SSE is used instead. There might be rare occasions where we should use old Intel FPU, like embedded Pentium clones but these are rare themselves.
 - synchronized
I kind of like the idea with synchronized classes. I don't think it is that hard to implement and could be a neat thing. Also helps Java portability.
 - alias this
Agreed, alias this should be removed and mixin templates should be used instead.
 - and parts of Phobos too.
You have to be more specific about this one.
Oct 25 2021
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Oct 25, 2021 at 02:34:24PM +0000, IGotD- via Digitalmars-d wrote:
 On Monday, 25 October 2021 at 13:42:19 UTC, Guillaume Piolat wrote:
 It's a great thing other people get to decide, because I would remove:
[...]
 - pure
Agreed, if you don't want a function messing around with a global state, don't mess around with a global state, easy. You don't need a badge for that. I'm sure there are holes in the pure rule that the compiler cannot detect as well.
I think the idea behind pure is for the compiler to enforce purity in your code, since human beings are prone to making mistakes. And also to optimize code in certain cases, though in practice that's so rare that it's not really that useful (IIRC, the only optimization currently performed applies only to strongly pure functions, and within a single expression, so that `f(x) + f(x)` calls `f` only once if f is strongly pure. Well, pure is also used in implicit casting to immutable in functions that construct objects. But this is also relatively rare and somewhat low impact. So I'm on the fence about `pure` in D. It promises a lot, but the current implementation is lackluster, and delivers only a little. If it had a bigger impact on optimization, say, it could be more useful. But currently its impact in practice is rather niche, and most of the time doesn't really affect much in code. (Perhaps except to generate annoying compile errors when something expects to be pure but isn't!)
 - shared
Agreed, shared is one of the biggest mess in D. This is where it is obvious where the D language designers lack in experience and knowledge. It is also a very complex issue so my advice is to rather to copy an existing design or not at all and rely more on manual concurrent "safety".
Agreed.
 -  property
Do we need it? Isn't this partly handled by UFCS.
property was incompletely implemented, lacks a clear vision, and currently is basically just bitrotting. With UFCS and optional parentheses, plus some questionable designs (i.e., `a.x = y` is rewritten as `a.x(y)` under some circumstances), property is basically redundant and affects very little. The only concrete case I can think of where it actually makes a difference is when you have a property function that returns by ref and the caller tries to take the address. Well, that and isInputRange stubbornly insists on property even though it technically doesn't need to. So yeah, niche use, incomplete implementation, low impact. Meh. Wouldn't miss it if it went the way of the dodo.
 - inout
Isn't this depreciated?
Not that I know of. And yeah, it's a mess. Very convenient in the simple cases, a nightmare to work with in non-trivial cases due to complicated interactions with the rest of the type system and incomplete functionality in that area.
 - GC-calling-destructors
GC calling destructors is often not used. However, there might be rare occasions we need it. Like knowing when something is being destroyed.
GC and dtors are just bad news in general... unless you don't care when something is destroyed (if ever), just that fact that it was destroyed.
 - virtual-by-default
Agreed, it should be final by default. This one of the rare occasions where the D maintainers agree.
Unfortunately this can't be changed without subtle breakage of a whole ton o' code. So, sadly, not gonna happen.
 - real
Agreed, real can be removed. Even Intel doesn't optimize their old FPU anymore and SSE is used instead. There might be rare occasions where we should use old Intel FPU, like embedded Pentium clones but these are rare themselves.
Real is nice in those rare circumstances where (1) the extra precision actually make a difference, and (2) the performance hit doesn't kill you. But yeah, it's not the best thing there is. Part of the problem is how std.math greedily converts to real and back (though IIRC this has been (partially?) fixed in recent releases). Also, it's the one type in D that doesn't have a fixed width, which can lead to surprising results in cross-platform code (though, admittedly, only rarely).
 - synchronized
I kind of like the idea with synchronized classes. I don't think it is that hard to implement and could be a neat thing. Also helps Java portability.
It's convenient for quick-n-dirty concurrent OO code where performance isn't critical. But if you want more modern concurrent techniques / high performance, yeah, it's not of much use. It's arguably dated technology.
 - alias this
Agreed, alias this should be removed and mixin templates should be used instead.
[...] `alias this` is awesome for refactoring code, though. When you want to substitute a new type for an existing one and want code that expect only the old type to work without a truckload of rewriting. But yeah, beyond a rather narrow scope of usefulness, it quickly leads to poor maintainability and other code smells. T -- Дерево держится корнями, а человек - друзьями.
Oct 25 2021
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Monday, 25 October 2021 at 16:13:25 UTC, H. S. Teoh wrote:
 I think the idea behind pure is for the compiler to enforce 
 purity in your code, since human beings are prone to making 
 mistakes.
Strong purity is useful when you use a library that takes a lambda expression that is meant to be used for comparison and the like, so you can prove that the library algorithm is correct if the input expression is strongly pure. D's weak purity does not seem to even require that the function is idempotent (that it has the same effect if called twice), so it does not really provide enough guarantees to verify the correctness of algorithms that take external code as input. So it isn't surprising that people have not found much use for it. Being able to put constraints on lambda-parameters to functions is very useful in general, but you might want stronger guarantees than D's «pure».
 - shared
Agreed, shared is one of the biggest mess in D. This is where it is obvious where the D language designers lack in experience and knowledge. It is also a very complex issue so my advice is to rather to copy an existing design or not at all and rely more on manual concurrent "safety".
Agreed.
Shared is actually one of the more promising aspects of D where it could stand out, but you need to be very pedantic when designing it (theoretical) and make sure that the resulting type system is sound. Meaning, you cannot allow "convenient hacks" and "pragmatic exceptions". It remains to be seen if D can adopt enough strong guarantees (be more principled) to make "shared" useful. I think it is possible to define shared in a potent manner, that gives optimization opportunities, but I am not sure if the majority of D users would embrace it.
 Unfortunately this can't be changed without subtle breakage of 
 a whole ton o' code.  So, sadly, not gonna happen.
I actually think a lot of things can be changed with invisible-breakage if you plan for it. All you have to do is to extend the compiler-internal semantics to accept new features, without contradicting the old compiler-internal semantics. When it comes to syntax you could just use a language version identifier per source file.
 It's convenient for quick-n-dirty concurrent OO code where 
 performance isn't critical. But if you want more modern 
 concurrent techniques / high performance, yeah, it's not of 
 much use.  It's arguably dated technology.
Synchronized has runtime debt. That is a higher-level language design choice and not really a system level design choice. You could redefine the semantics so you don't have to account for it in the runtime, I think.
Oct 25 2021
parent reply IGotD- <nise nise.com> writes:
On Monday, 25 October 2021 at 17:17:14 UTC, Ola Fosheim Grøstad 
wrote:
 Shared is actually one of the more promising aspects of D where 
 it could stand out, but you need to be very pedantic when 
 designing it (theoretical) and make sure that the resulting 
 type system is sound. Meaning, you cannot allow "convenient 
 hacks" and "pragmatic exceptions". It remains to be seen if D 
 can adopt enough strong guarantees (be more principled) to make 
 "shared" useful.

 I think it is possible to define shared in a potent manner, 
 that gives optimization opportunities, but I am not sure if the 
 majority of D users would embrace it.


 It's convenient for quick-n-dirty concurrent OO code where 
 performance isn't critical. But if you want more modern 
 concurrent techniques / high performance, yeah, it's not of 
 much use.  It's arguably dated technology.
Synchronized has runtime debt. That is a higher-level language design choice and not really a system level design choice. You could redefine the semantics so you don't have to account for it in the runtime, I think.
There are a few rules I've discovered with concurrent programming. Always take the lock, don't dwell into atomic operations/structures too much. 99% of the cases should be handled by traditional mutex/semaphores and possible language layers above that. Synchronized classes are good because the lock is implicit. However, it might be inefficient if you use several methods after each other which means several lock/unlock (Acquire/Release or whatever you name it) after each other. One of the best designs in Rust was to combine the borrowing with acquiring the lock. Then you can borrow/lock on a structure do all the operations you want as in normal single threaded programming and it will be released automatically when the borrow goes out of scope. This is is a genius design and I'm not sure how to pry it into D. Then we have the shared structs/classes in D where all basic types are forced to be atomic, which is totally insane. Lock free algorithms often consist of both normal and atomic variables. Also if you have several atomic variables, the operations on them together often introduce race conditions and your structure is not thread safe at all. When you are in lock free territory you are on your own and shared should only mean, this can be safely used from several threads at the same time. Compiler should do nothing more. Then we can also combine lock free structures with locked structures and where these are appropriate depends on the use case. Basically, the compiler should stay away from any further assumptions.
Oct 25 2021
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 25 October 2021 at 18:04:19 UTC, IGotD- wrote:
 Then we have the shared structs/classes in D where all basic 
 types are forced to be atomic, which is totally insane.
As far as I know this is not what `shared` does, and not what `shared` is intended to do. By itself, `shared` is just a marker for data that requires synchronization to access (what [the spec][1] calls "shared memory locations"). Whether that synchronization is accomplished using atomic operations or locking is entirely up to the programmer. [1]: https://dlang.org/spec/intro.html
Oct 25 2021
next sibling parent reply IGotD- <nise nise.com> writes:
On Monday, 25 October 2021 at 18:12:36 UTC, Paul Backus wrote:
 As far as I know this is not what `shared` does, and not what 
 `shared` is intended to do. By itself, `shared` is just a 
 marker for data that requires synchronization to access (what 
 [the spec][1] calls "shared memory locations"). Whether that 
 synchronization is accomplished using atomic operations or 
 locking is entirely up to the programmer.

 [1]: https://dlang.org/spec/intro.html
Last time I tried a shared struct I had to cast away the atomic operations on about every line. Is this removed in newer compiler versions?
Oct 25 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 25 October 2021 at 18:22:59 UTC, IGotD- wrote:
 On Monday, 25 October 2021 at 18:12:36 UTC, Paul Backus wrote:
 As far as I know this is not what `shared` does, and not what 
 `shared` is intended to do. By itself, `shared` is just a 
 marker for data that requires synchronization to access (what 
 [the spec][1] calls "shared memory locations"). Whether that 
 synchronization is accomplished using atomic operations or 
 locking is entirely up to the programmer.

 [1]: https://dlang.org/spec/intro.html
Last time I tried a shared struct I had to cast away the atomic operations on about every line. Is this removed in newer compiler versions?
I think perhaps you are mistaking the suggestion to use atomic operations in the compiler's error message for the actual presence of atomic operations in the code. ```d shared int x; void main() { x += 1; // Error: read-modify-write operations are not allowed for `shared` variables // Use `core.atomic.atomicOp!"+="(x, 1)` instead } ``` The compiler requires you to use *some* kind of synchronization to modify `x`. The error message (perhaps misguidedly) *suggests* using atomic operations, but they are not actually required--you could also use a mutex. If you're using a mutex, you do have to cast away `shared` once you have locked it, since in general the compiler has no way of knowing which mutex is associated with which variable.
Oct 25 2021
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Monday, 25 October 2021 at 18:59:51 UTC, Paul Backus wrote:
 The compiler requires you to use *some* kind of synchronization 
 to modify `x`. The error message (perhaps misguidedly) 
 *suggests* using atomic operations, but they are not actually 
 required--you could also use a mutex.
100% misguided, and the type system should not allow it. It cannot be assumed to be safe.
 If you're using a mutex, you do have to cast away `shared` once 
 you have locked it, since in general the compiler has no way of 
 knowing which mutex is associated with which variable.
And this is where almost all utility of shared is lost. Now you can no longer assume that something that isn't marked as shared is thread local... What is left is syntactical clutter.
Oct 25 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 25 October 2021 at 19:12:52 UTC, Ola Fosheim Grøstad 
wrote:
 On Monday, 25 October 2021 at 18:59:51 UTC, Paul Backus wrote:
 The compiler requires you to use *some* kind of 
 synchronization to modify `x`. The error message (perhaps 
 misguidedly) *suggests* using atomic operations, but they are 
 not actually required--you could also use a mutex.
100% misguided, and the type system should not allow it. It cannot be assumed to be safe.
It can be assumed not to cause a data race, which means that an atomic operation on a `shared` variable is exactly as safe as the corresponding non-atomic operation on a thread-local variable.
 If you're using a mutex, you do have to cast away `shared` 
 once you have locked it, since in general the compiler has no 
 way of knowing which mutex is associated with which variable.
And this is where almost all utility of shared is lost. Now you can no longer assume that something that isn't marked as shared is thread local...
[The language spec][1] defines "thread local" as follows:
 *Thread-local memory locations* are accessible from only one 
 thread at a time.
And further clarifies that
 A memory location can be temporarily transferred from shared to 
 local if synchronization is used to prevent any other threads 
 from accessing the memory location during the operation.
Of course, the compiler will not stop you from writing incorrect casts in ` system` code, but that's not an issue unique to `shared`. [1]: https://dlang.org/spec/intro.html
Oct 25 2021
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Monday, 25 October 2021 at 19:52:10 UTC, Paul Backus wrote:
 It can be assumed not to cause a data race, which means that an 
 atomic operation on a `shared` variable is exactly as safe as 
 the corresponding non-atomic operation on a thread-local 
 variable.
Of course it can't. Let take the simplest of the simple; a struct with a date with 3 fields_ day, month and year. Now, let us assume "2021-10-31". And then we add one day using atomics? How? If I declare something as shared as a type, then I expect some solid means to protect it. The current setup is too naive to be useful. It is no better than a wrapper-template. It does not need to implemented in the type system. It could have been implemented as a regular library. This is not to say that the concept of "shared" is not useful. It is the might-as-well-have-been-a-template-wrapper-approach that is useless.
 [The language spec][1] defines "thread local" as follows:

 *Thread-local memory locations* are accessible from only one 
 thread at a time.
That is way too weak to get the benefits that are desirable, meaning: a competitive edge over C++. I can create a template-wrapper in C++ too. So, as is, "shared" provides no advantage as far as I can tell.
 And further clarifies that

 A memory location can be temporarily transferred from shared 
 to local if synchronization is used to prevent any other 
 threads from accessing the memory location during the 
 operation.
Of course, the compiler will not stop you from writing incorrect casts in ` system` code, but that's not an issue unique to `shared`.
I don't see how this can be guaranteed. The compiler _needs_ to know where the line in the sand is drawn so that is isn't limited by the potentially performance-limiting sequencing points that C++ has to deal with. For instance: what are the lifetimes for cached computations when you don't know if another thread will obtain access to what you received as a "nonshared object"? Also, in order to get solid GC performance the compiler needs to know whether the memory is owned by the thread or is foreign to it. "shared" has to be more than a shell in order to enable more "power"...
Oct 25 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 25 October 2021 at 20:12:04 UTC, Ola Fosheim Grøstad 
wrote:
 On Monday, 25 October 2021 at 19:52:10 UTC, Paul Backus wrote:
 It can be assumed not to cause a data race, which means that 
 an atomic operation on a `shared` variable is exactly as safe 
 as the corresponding non-atomic operation on a thread-local 
 variable.
Of course it can't. Let take the simplest of the simple; a struct with a date with 3 fields_ day, month and year. Now, let us assume "2021-10-31". And then we add one day using atomics? How?
I agree that trying to do this with atomics will not give you the right answer, but it is at least guaranteed not to cause undefined behavior (excluding contract/assertion failures). Safety does not imply correctness.
 *Thread-local memory locations* are accessible from only one 
 thread at a time.
That is way too weak to get the benefits that are desirable, meaning: a competitive edge over C++. I can create a template-wrapper in C++ too. So, as is, "shared" provides no advantage as far as I can tell.
The advantages are: 1. In C++, such a template wrapper would be opt-in. In D, `shared` is opt-out. 2. ` safe` D code can assume that anything non-`shared` is thread-local, because only ` system` or ` trusted` code can cast to and from `shared`. In other words, it makes code easier to reason about and concurrency bugs easier to isolate. You are probably correct that `shared` is not very useful for enabling compiler optimizations relative to what is possible in C++.
Oct 25 2021
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Monday, 25 October 2021 at 20:41:25 UTC, Paul Backus wrote:
 In other words, it makes code easier to reason about and 
 concurrency bugs easier to isolate.
That remains to be seen? There is really nothing that prevents another thread from writing to something that safe code has access to. So not sure how this is a better situation than C++ has... I somehow doubt that such surface semantics are enough for people to convince themselves that the hazzle of dealing with a feature is worth it (outside the most enthusiastic D programmers). Shared ends up a bit like transitive const and pure: you could, but won't, because it doesn't appear to provide any real edge. So why bother satisfying a whining compiler if you can avoid it altogether?
 You are probably correct that `shared` is not very useful for 
 enabling compiler optimizations relative to what is possible in 
 C++.
It will be very difficult for D to grow its own niche if what distinguishes it from other languages is primarily on the surface level. Rust is gaining ground on C++ because it is good at something that C++ cannot be good at, and that is probably also the only reason for why it is gaining ground?
Oct 25 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 25 October 2021 at 20:59:54 UTC, Ola Fosheim Grøstad 
wrote:
 On Monday, 25 October 2021 at 20:41:25 UTC, Paul Backus wrote:
 In other words, it makes code easier to reason about and 
 concurrency bugs easier to isolate.
That remains to be seen? There is really nothing that prevents another thread from writing to something that safe code has access to. So not sure how this is a better situation than C++ has...
Sure, there's nothing preventing ` system` code from causing undefined behavior. The difference is that in C++, all of your code is ` system`; in D, only some of it is.
Oct 25 2021
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Monday, 25 October 2021 at 21:49:15 UTC, Paul Backus wrote:
 Sure, there's nothing preventing ` system` code from causing 
 undefined behavior. The difference is that in C++, all of your 
 code is ` system`; in D, only some of it is.
Yes, that is true. I don't write a lot of multi-threaded code, but when I do I tend to keep that to a small set of files/data-structures. As such I am keeping a " system" subset in my source. It isn't checked by a compiler, but I am not sure that makes a big difference as I tend to encapsulate the " system" parts using abstractions. So if there is any benefit to the current version of "shared" it has to come from interacting with libraries. But there is little protection there since libraries can put themselves into " trusted" mode at their own will? So, then the remaining advantage would be to have the opportunity to grep for system/ trusted in library code... I am not convinced that this is enough to be a significant advantage as I am not inclined to use libraries that aren't well-behaved (if they are a source for bugs, I'll most likely replace them wholesale).
Oct 25 2021
prev sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Monday, 25 October 2021 at 18:12:36 UTC, Paul Backus wrote:
 As far as I know this is not what `shared` does, and not what 
 `shared` is intended to do. By itself, `shared` is just a 
 marker for data that requires synchronization to access (what 
 [the spec][1] calls "shared memory locations"). Whether that 
 synchronization is accomplished using atomic operations or 
 locking is entirely up to the programmer.
I think shared is not usable as it stands today, so I have no experience with it, but unless my memory is playing tricks with me; I think someone pushed for allowing atomic access to struct fields of a shared struct and that this was accepted by Walter? If I am wrong, then I apologize.
Oct 25 2021
prev sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Monday, 25 October 2021 at 18:04:19 UTC, IGotD- wrote:
 Always take the lock, don't dwell into atomic 
 operations/structures too much. 99% of the cases should be 
 handled by traditional mutex/semaphores and possible language 
 layers above that.
Hm, in real-time scenarios you cannot take locks, so for system level programming you have to deal with atomics. It does get tricky real fast though… so make the data model as simple as possible and don't focus too much on micro-optimization in the beginning (just be happy if you can convince yourself that it is provably correct).
 Synchronized classes are good because the lock is implicit. 
 However, it might be inefficient if you use several methods 
 after each other which means several lock/unlock 
 (Acquire/Release or whatever you name it) after each other.
Yes, I wouldn't have added the feature, but once it is there it is an easy to understand starting point. Monitors is also something that is taught at universities so it is a concept "generic" programmers would (should) know.
 One of the best designs in Rust was to combine the borrowing 
 with acquiring the lock. Then you can borrow/lock on a 
 structure do all the operations you want as in normal single 
 threaded programming and it will be released automatically when 
 the borrow goes out of scope. This is is a genius design and 
 I'm not sure how to pry it into D.
I don't know enough about Rust's approach here, but in general, effective locking strategies cannot easily be formulated in a way that a C-ish language compiler can reason about. For that you would need a much more advanced layer, I think. So, not really realistic for D in the general case(and probably not for Rust either).
 Then we have the shared structs/classes in D where all basic 
 types are forced to be atomic, which is totally insane.
This is the case where D gets bitten by "being pragmatic" because of users pushing for convenience. And it isn't safe to allow access to shared state that way, or rather; it does not encourage correctness. Meaning, it would only be safe for very simple models. What is needed is some kind of effect system that where "shared unaccessible" is turned into "shared accessible" (but not non-shared). I guess this is what Rust kinda touches upon. This might be difficult to do properly, so it can also be unverified by the compiler, but you should be able to draw "lines in the sand" in your code, somehow. (I don't have the answer, but I think there are opportunities.)
Oct 25 2021
prev sibling parent reply Dukc <ajieskola gmail.com> writes:
On Monday, 25 October 2021 at 14:34:24 UTC, IGotD- wrote:
 - pure
Agreed, if you don't want a function messing around with a global state, don't mess around with a global state, easy. You don't need a badge for that. I'm sure there are holes in the pure rule that the compiler cannot detect as well.
`pure` is also supposed to be an optimization aid. I was about to say that you're right about that holes thing, because of stuff like this one: ```d pure short coming() { typeof(return) result = void; return result; //may return anything } ``` But when you think of it, why should this be a problem? This one returns an implementation defined value. If a compiler skips repeated calls to this one because of the `pure` attribute and just reuses the value from the first call, so what? Because the return values are implementation defined, the compiler is free to have them all to be the same with regards to each other.
Oct 27 2021
next sibling parent reply IGotD- <nise nise.com> writes:
On Wednesday, 27 October 2021 at 19:19:31 UTC, Dukc wrote:
 `pure` is also supposed to be an optimization aid.
It would be interesting to see an example of how pure can help optimization, compared to if you wouldn't badge the function.
Oct 27 2021
next sibling parent Dukc <ajieskola gmail.com> writes:
On Wednesday, 27 October 2021 at 19:26:57 UTC, IGotD- wrote:
 On Wednesday, 27 October 2021 at 19:19:31 UTC, Dukc wrote:
 `pure` is also supposed to be an optimization aid.
It would be interesting to see an example of how pure can help optimization, compared to if you wouldn't badge the function.
For example, with that `coming` function, the compiler may rewrite ```d foreach(unused; 0 .. 4) coming.writeln; ``` to ```d auto comingRes = coming; foreach(unused; 0 .. 4) comingRes.writeln; ``` . In fact, the compiler can even evaluate `coming` at compile time, and avoid any function call at all: ```d foreach(unused; 0 .. 4) { //or any other short value 29677.writeln; } ```
Oct 27 2021
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Oct 27, 2021 at 07:26:57PM +0000, IGotD- via Digitalmars-d wrote:
 On Wednesday, 27 October 2021 at 19:19:31 UTC, Dukc wrote:
 
 `pure` is also supposed to be an optimization aid.
It would be interesting to see an example of how pure can help optimization, compared to if you wouldn't badge the function.
The following example supposedly shows the difference: int pureFunc() pure { return 1; } int impureFunc() { return 1; } void main() { writeln(pureFunc() + pureFunc()); // compiler may call pureFunc only once writeln(impureFunc() + impureFunc()); // compiler must call impureFunc twice } Unfortunately, a quick test with dmd did not show the expected optimization. Which confirms what I said before that pure's optimization benefit is really rather marginal. The bigger benefit is the mechanical verification that you didn't accidentally depend on global state when you didn't want to, which can help with improving code quality. T -- Which is worse: ignorance or apathy? Who knows? Who cares? -- Erich Schubert
Oct 27 2021
next sibling parent reply Dennis <dkorpel gmail.com> writes:
On Wednesday, 27 October 2021 at 19:53:52 UTC, H. S. Teoh wrote:
 Unfortunately, a quick test with dmd did not show the expected 
 optimization.  Which confirms what I said before that pure's 
 optimization benefit is really rather marginal.
The function needs to be `nothrow` and compiled with `-O -release`, because dmd needs to account for exceptions / errors, and even then the optimization [shouldn't really be done](https://issues.dlang.org/show_bug.cgi?id=22277). LDC doesn't do anything with `pure` for optimizations, but since it has cross module inlining, it doesn't need it.
 The bigger benefit is the mechanical verification that you 
 didn't accidentally depend on global state when you didn't want 
 to, which can help with improving code quality.
That, but it can also help proving a lack of aliasing. For example: ```D int[] duplicate(scope int[] x) pure; ``` The returned array can safely be converted to `immutable int[]`. (N.b. this is not implemented yet) ```D void foo(int[] x, int[] y) pure nothrow; ``` Parameters `x` and `y` can freely be inferred `scope`, which is useful since dmd's `scope` inference from function bodies is not good.
Oct 28 2021
parent reply Dukc <ajieskola gmail.com> writes:
On Thursday, 28 October 2021 at 10:53:25 UTC, Dennis wrote:
 On Wednesday, 27 October 2021 at 19:53:52 UTC, H. S. Teoh wrote:
 Unfortunately, a quick test with dmd did not show the expected 
 optimization.  Which confirms what I said before that pure's 
 optimization benefit is really rather marginal.
The function needs to be `nothrow` and compiled with `-O -release`, because dmd needs to account for exceptions / errors, and even then the optimization [shouldn't really be done](https://issues.dlang.org/show_bug.cgi?id=22277). LDC doesn't do anything with `pure` for optimizations, but since it has cross module inlining, it doesn't need it.
I think that, in addition to bugs, there is one hurdle in letting the compiler to fully optimise based on `pure`: how do we implement a function that manually frees an array? If the signature is ```d pure nothrow nogc void free(int[]) ``` , the compiler might elide consecutive frees to different arrays with the same content (so that any potential crash or infinite loop would happen in first `free`), if it can detect that the freed array does not alias to anything and any potential mutation of the array would have no effect.
 The bigger benefit is the mechanical verification that you 
 didn't accidentally depend on global state when you didn't 
 want to, which can help with improving code quality.
That, but it can also help proving a lack of aliasing. For example: ```D int[] duplicate(scope int[] x) pure; ``` The returned array can safely be converted to `immutable int[]`. (N.b. this is not implemented yet)
Great, I can `malloc` immutable data with this feature without casting once implemented.
 ```D
 void foo(int[] x, int[] y) pure nothrow;
 ```
 Parameters `x` and `y` can freely be inferred `scope`, which is 
 useful since dmd's `scope` inference from function bodies is 
 not good.
If `-preview=dip1021` (` live` checking) isn't enabled, that is.
Oct 28 2021
next sibling parent reply Dennis <dkorpel gmail.com> writes:
On Thursday, 28 October 2021 at 15:18:54 UTC, Dukc wrote:
 I think that, in addition to bugs, there is one hurdle in 
 letting the compiler to fully optimise based on `pure`: how do 
 we implement a function that manually frees an array?
That's exactly what the linked issue ([22277](https://issues.dlang.org/show_bug.cgi?id=22277)) is about, so if you have any more insights on that, please post them there.
Oct 28 2021
parent reply Dukc <ajieskola gmail.com> writes:
On Thursday, 28 October 2021 at 15:40:21 UTC, Dennis wrote:
 On Thursday, 28 October 2021 at 15:18:54 UTC, Dukc wrote:
 I think that, in addition to bugs, there is one hurdle in 
 letting the compiler to fully optimise based on `pure`: how do 
 we implement a function that manually frees an array?
That's exactly what the linked issue ([22277](https://issues.dlang.org/show_bug.cgi?id=22277)) is about, so if you have any more insights on that, please post them there.
No that's slightly different. The linked issue deals with strongly `pure` functions. I'm dreaming about letting the compiler to optimise based on weak `pure` too - not currently allowed if I read the spec right, but could be without this issue (I think?).
Oct 28 2021
parent reply max haughton <maxhaton gmail.com> writes:
On Thursday, 28 October 2021 at 16:36:30 UTC, Dukc wrote:
 On Thursday, 28 October 2021 at 15:40:21 UTC, Dennis wrote:
 On Thursday, 28 October 2021 at 15:18:54 UTC, Dukc wrote:
 I think that, in addition to bugs, there is one hurdle in 
 letting the compiler to fully optimise based on `pure`: how 
 do we implement a function that manually frees an array?
That's exactly what the linked issue ([22277](https://issues.dlang.org/show_bug.cgi?id=22277)) is about, so if you have any more insights on that, please post them there.
No that's slightly different. The linked issue deals with strongly `pure` functions. I'm dreaming about letting the compiler to optimise based on weak `pure` too - not currently allowed if I read the spec right, but could be without this issue (I think?).
Is this worth caring about? Do the backend a that actually matter not already perform this analysis as part of their IPA?
Oct 28 2021
parent reply Dukc <ajieskola gmail.com> writes:
On Thursday, 28 October 2021 at 21:48:09 UTC, max haughton wrote:
 No that's slightly different. The linked issue deals with 
 strongly `pure` functions. I'm dreaming about letting the 
 compiler to optimise based on weak `pure` too - not currently 
 allowed if I read the spec right, but could be without this 
 issue (I think?).
Is this worth caring about?
You decide. Without that, the weak `pure` can still be used inside strongly pure functions, but is otherwise useless for optimisation.
 Do the backend a that actually matter not already perform this 
 analysis as part of their IPA?
If the function body is available and not too complicated, probably. But with `pure` it's possible for a compiler to optimise based on the signature alone. I don't personally care that much about having a super-optimising compiler, but I still wish that our attributes provide as much info as possible for any analysis program.
Oct 28 2021
parent max haughton <maxhaton gmail.com> writes:
On Thursday, 28 October 2021 at 23:05:04 UTC, Dukc wrote:
 On Thursday, 28 October 2021 at 21:48:09 UTC, max haughton 
 wrote:
 No that's slightly different. The linked issue deals with 
 strongly `pure` functions. I'm dreaming about letting the 
 compiler to optimise based on weak `pure` too - not currently 
 allowed if I read the spec right, but could be without this 
 issue (I think?).
Is this worth caring about?
You decide. Without that, the weak `pure` can still be used inside strongly pure functions, but is otherwise useless for optimisation.
 Do the backend a that actually matter not already perform this 
 analysis as part of their IPA?
If the function body is available and not too complicated, probably. But with `pure` it's possible for a compiler to optimise based on the signature alone. I don't personally care that much about having a super-optimising compiler, but I still wish that our attributes provide as much info as possible for any analysis program.
I still think this analysis is going to be done anyway by either GCC or LLVM. Also, is elision even valid in the general case, what if the function is weak. It can't be elided until link-time in some rare but not impossible circumstances.
Oct 28 2021
prev sibling parent reply Patrick Schluter <Patrick.Schluter bbox.fr> writes:
On Thursday, 28 October 2021 at 15:18:54 UTC, Dukc wrote:
 On Thursday, 28 October 2021 at 10:53:25 UTC, Dennis wrote:
 On Wednesday, 27 October 2021 at 19:53:52 UTC, H. S. Teoh 
 wrote:
 [...]
The function needs to be `nothrow` and compiled with `-O -release`, because dmd needs to account for exceptions / errors, and even then the optimization [shouldn't really be done](https://issues.dlang.org/show_bug.cgi?id=22277). LDC doesn't do anything with `pure` for optimizations, but since it has cross module inlining, it doesn't need it.
I think that, in addition to bugs, there is one hurdle in letting the compiler to fully optimise based on `pure`: how do we implement a function that manually frees an array? If the signature is ```d pure nothrow nogc void free(int[]) ```
Not at all. free cannot, by its semantic, be pure (same for malloc). Trying to make free pure is a silly challenge.
Oct 28 2021
parent reply Dukc <ajieskola gmail.com> writes:
On Thursday, 28 October 2021 at 19:46:32 UTC, Patrick Schluter 
wrote:
 Not at all. free cannot, by its semantic, be pure (same for 
 malloc). Trying to make free pure is a silly challenge.
https://dlang.org/phobos/core_memory.html#.pureFree
Oct 28 2021
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Oct 28, 2021 at 10:48:10PM +0000, Dukc via Digitalmars-d wrote:
 On Thursday, 28 October 2021 at 19:46:32 UTC, Patrick Schluter wrote:
 Not at all. free cannot, by its semantic, be pure (same for malloc).
 Trying to make free pure is a silly challenge.
https://dlang.org/phobos/core_memory.html#.pureFree
Such a function cannot be strongly pure; at best it can only be weakly pure. IMO it's a bug that a function that takes an immutable pointer and frees it can be strongly pure. That breaks the "no visible change to the outside world" principle of strong purity and invalidates optimizations based on strong purity. A weakly pure function rightly cannot be elided in an expression that calls it multiple times, because weak purity is not strong enough to guarantee no side-effects. T -- For every argument for something, there is always an equal and opposite argument against it. Debates don't give answers, only wounded or inflated egos.
Oct 28 2021
prev sibling parent reply Patrick Schluter <Patrick.Schluter bbox.fr> writes:
On Thursday, 28 October 2021 at 22:48:10 UTC, Dukc wrote:
 On Thursday, 28 October 2021 at 19:46:32 UTC, Patrick Schluter 
 wrote:
 Not at all. free cannot, by its semantic, be pure (same for 
 malloc). Trying to make free pure is a silly challenge.
https://dlang.org/phobos/core_memory.html#.pureFree
I don't understand it. It does not make any sense. pure functions are function that do not depend on global state for their result. Weak purity allows for some exception like for a print function which has global effects but these effects have no feedback and can ne ignored. This is not the case with allocation/free, which are, by defintion, dependend on a global state (even if only thread local). Each call to malloc, by definition must return another value and/or can return a same value with other parameter. The result does not depend on ANYTHING in its scope. pure allocation/free is a recipe for disaster. My understanding of purity at l east.
Oct 29 2021
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Oct 29, 2021 at 03:51:05PM +0000, Patrick Schluter via Digitalmars-d
wrote:
 On Thursday, 28 October 2021 at 22:48:10 UTC, Dukc wrote:
 On Thursday, 28 October 2021 at 19:46:32 UTC, Patrick Schluter wrote:
 Not at all. free cannot, by its semantic, be pure (same for
 malloc).  Trying to make free pure is a silly challenge.
https://dlang.org/phobos/core_memory.html#.pureFree
I don't understand it. It does not make any sense. pure functions are function that do not depend on global state for their result. Weak purity allows for some exception like for a print function which has global effects but these effects have no feedback and can ne ignored.
Actually, even that is wrong. I/O changes global state, and as such have no place in pure functions, not even weakly pure ones. Weakly pure functions may only modify *state they receive via their parameters*, and nothing else. Modifying any other state violates the definition of (weak) purity and breaks any purity-based optimizations. The only I/O permitted in pure functions, whether weakly pure or strongly pure, are in debug statements, and that's only for the purposes of debugging. It should definitely not be something program semantics depend on.
 This is not the case with allocation/free, which are, by defintion,
 dependend on a global state (even if only thread  local).
Yeah, pureFree makes no sense at all. It's a disaster waiting to happen.
 Each call to malloc, by definition must return another value and/or
 can return a same value with other parameter. The result does not
 depend on ANYTHING in its scope.
 pure allocation/free is a recipe for disaster.
 My understanding of purity at l east.
I agree. I don't know when/why pureFree was introduced, but it's all kinds of wrong. One may argue that pure functions may return allocated memory (this is explicitly allowed in the specs), but IMO this is only allowed if the allocation is *implicit*, i.e., part of a language construct, such that its effects are not visible within the domain of language constructs. Otherwise this opens the door to all kinds of subtle bugs that basically make purity a laughably useless construct. T -- Computerese Irregular Verb Conjugation: I have preferences. You have biases. He/She has prejudices. -- Gene Wirchenko
Oct 29 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 29 October 2021 at 16:08:10 UTC, H. S. Teoh wrote:
 This is not the case with allocation/free, which are, by 
 defintion, dependend on a global state (even if only thread  
 local).
Yeah, pureFree makes no sense at all. It's a disaster waiting to happen.
I think the original sin here is allowing GC allocation (`new`, `~=`, closures) to be `pure`, for "pragmatic" reasons. Once you've done that, it's not hard to justify adding `pureMalloc` too. And once you have that, why not `pureFree`? It's just a little white lie; surely nobody will get hurt. Of course the end result is that `pure` ends up being basically useless for anything beyond linting, and can't be fixed without breaking lots of existing code.
Oct 29 2021
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Oct 29, 2021 at 04:14:35PM +0000, Paul Backus via Digitalmars-d wrote:
 On Friday, 29 October 2021 at 16:08:10 UTC, H. S. Teoh wrote:
 This is not the case with allocation/free, which are, by
 defintion, dependend on a global state (even if only thread
 local).
Yeah, pureFree makes no sense at all. It's a disaster waiting to happen.
I think the original sin here is allowing GC allocation (`new`, `~=`, closures) to be `pure`, for "pragmatic" reasons. Once you've done that, it's not hard to justify adding `pureMalloc` too. And once you have that, why not `pureFree`? It's just a little white lie; surely nobody will get hurt. Of course the end result is that `pure` ends up being basically useless for anything beyond linting, and can't be fixed without breaking lots of existing code.
I think the real root problem is mixing incompatible levels of abstraction. At some level of abstraction, one could argue that GC allocation (or memory allocation in general) is an intrinsic feature of the layer of abstraction you're working with: a bunch of functions that do computations with arrays can be considered pure if the implementation of said arrays is abstracted away by the language, and these functions use only the array primitives given to them by the abstraction, i.e., they don't allocate or free memory directly, so they do not directly observe the external effects of allocation. Think of a program in a functional language, for example. The implementation is definitely changing global state -- doing I/O, allocating/freeing memory, etc.. But at the abstraction level of the function language itself, these implementation details are hidden away and one can meaningfully speak of the purity of functions written in that language. One may legally optimize code based on the abstracted semantics, because the semantics at the higher level are preserved in spite of the low-level implementation details being changed. The problems come, however, when you have code that operates *both* at the abstract level *and* deal with the low-level implementation at the same time. Suddenly, there is no longer a clear separation between code in the higher-level abstraction and the lower-level implementation where you have to deal with dirty details like allocating and freeing memory. So the assumptions that the higher-level abstraction provides may no longer hold, and that's where you begin to run into trouble. Optimizations based on guarantees provided by the higher-level abstraction become invalidated by lower-level code that break these assumptions (because they operate outside of the confines of the higher-level abstraction). This is why array manipulation in a D pure function is in some sense permissible, under certain assumptions, but things like pureFree do not make sense, because it clearly mixes incompatible levels of abstraction in a way that will inevitably lead to problems. If we were to permit array allocations in pure code, then we must necessarily also commit to not go outside of the confines of that level of abstraction -- i.e., we are not allowed to use memory allocation primitives that said array operations are based on. As soon as this is violated, the whole thing comes crashing down, because your program now has some operations that are outside the abstraction assumed by the optimizations based on `pure`. Meaning that these optimizations now may be invalid. The situation is similar to `immutable`. If you're operating at the GC level, there is strictly speaking no such thing as immutable, because the GC code casts untyped memory into immutable and vice versa, so that the same block of memory may be immutable at one point in time but become mutable when it's later collected and reallocated to mutable data. But this does not mean we're not allowed to optimize based on immutable; by the same line of argument we might as well throw const and immutable to the winds. Instead, we declare GC code as system, with the GC interface trusted, i.e., the GC operates outside of the confines of immutability, but we trust it to do its job properly so that when we return to the higher-level abstraction, all our previous assumptions about immutable continue to hold. So for pure, it's the same thing. For something to be pure you must have a well-defined set of abstractions based on which the optimizer is allowed to make certain transformations to your code. You must adhere to the restrictions imposed by this abstraction -- which is what the `pure` qualifier is ostensibly for -- otherwise you end up in UB territory, just like the situation with casting away immutable. The only sane way to maintain D's purity system is that code marked pure cannot contain anything that violates the assumptions we have imposed on pure. Otherwise we're in de facto UB territory even if the spec's definition of UB doesn't specifically state this case. Long story short, pureFree makes no sense because it's very intent is to make a visible change to the global state of memory -- clearly at a much lower level of abstraction than `pure` is intended to operate at, and clearly outside the `pure` abstraction. In fact, I'd say that *anything* that explicitly allocates/deallocates memory ought to be prohibited from being marked `pure`. Array operations are OK if we view them as intrinsic, opaque operations that the pure abstraction grants us. But anything that explicitly deals with memory allocation is clearly an operation outside the `pure` abstraction, so allowing it to be marked `pure` will inevitably break assumptions and land us in trouble. T -- Meat: euphemism for dead animal. -- Flora
Oct 29 2021
next sibling parent reply Adam Ruppe <destructionator gmail.com> writes:
On Friday, 29 October 2021 at 21:16:49 UTC, H. S. Teoh wrote:
 Long story short, pureFree makes no sense
What about: void foo() pure { int* a = malloc(5); scope(exit) free(a); } How is that any different than void foo() pure { int[5] a; } ? I expect that's how it is actually used. (Perhaps it would be better encapsulated in a function along the lines of: void foo() pure { workWithMemory((int* a) { }, 5); } And then the malloc/free could be wrapped up a bit better)
Oct 29 2021
next sibling parent reply Patrick Schluter <Patrick.Schluter bbox.fr> writes:
On Friday, 29 October 2021 at 21:56:10 UTC, Adam Ruppe wrote:
 On Friday, 29 October 2021 at 21:16:49 UTC, H. S. Teoh wrote:
 Long story short, pureFree makes no sense
What about: void foo() pure { int* a = malloc(5); scope(exit) free(a); }
foo is pure, but malloc and free aren't individually. If the declaration of the purity of foo is curtailed because malloc and free have to be marked as pure, then it is a failure of the language. In fact there should be an equivalent of trusted for purity, telling the compiler "trust me, I know that that combination of impure functions is on a whole pure". Marking malloc/free as pure doesn't cut it as these functions cannot, by definition, be pure individually. But I start to understand where this abomination of purity comes from. Transitivity. I suspect that applying transitivity unthinkingly is not such a good idea as can be seen also with const (immutable is transitive as it describes a property of the data, const is not as it is a property of the means to access the data, not the data itself).
 How is that any different than

 void foo() pure {
    int[5] a;
 }

 ?

 I expect that's how it is actually used. (Perhaps it would be 
 better encapsulated in a function along the lines of:

 void foo() pure {
     workWithMemory((int* a) {

     }, 5);
 }


 And then the malloc/free could be wrapped up a bit better)
Oct 30 2021
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Oct 30, 2021 at 10:17:56AM +0000, Patrick Schluter via Digitalmars-d
wrote:
 On Friday, 29 October 2021 at 21:56:10 UTC, Adam Ruppe wrote:
 On Friday, 29 October 2021 at 21:16:49 UTC, H. S. Teoh wrote:
 Long story short, pureFree makes no sense
What about: void foo() pure { int* a = malloc(5); scope(exit) free(a); }
foo is pure, but malloc and free aren't individually. If the declaration of the purity of foo is curtailed because malloc and free have to be marked as pure, then it is a failure of the language. In fact there should be an equivalent of trusted for purity, telling the compiler "trust me, I know that that combination of impure functions is on a whole pure". Marking malloc/free as pure doesn't cut it as these functions cannot, by definition, be pure individually.
Exactly, we need an escape hatch to tell the compiler "this sequence of operations are individually impure, but as a whole they are pure". Just like a sequence of pointer arithmetic operations are individually unsafe, but if properly constructed they may, as a whole, be safe, so we have trusted for that purpose.
 But I start to understand where this abomination of purity comes from.
 Transitivity. I suspect that applying transitivity unthinkingly is not
 such a good idea as can be seen also with const (immutable is
 transitive as it describes a property of the data, const is not as it
 is a property of the means to access the data, not the data itself).
But how else can pure be implemented? If we remove transitivity from pure, then it essentially becomes an anemic recommendation for programming by convention, rather than something enforceable by the compiler. Sometimes an escape hatch is needed, but that doesn't mean the rest of the time we can just get away with whatever we want. T -- A computer doesn't mind if its programs are put to purposes that don't match their names. -- D. Knuth
Nov 02 2021
parent reply Patrick Schluter <Patrick.Schluter bbox.fr> writes:
On Tuesday, 2 November 2021 at 18:18:56 UTC, H. S. Teoh wrote:
 On Sat, Oct 30, 2021 at 10:17:56AM +0000, Patrick Schluter via 
 Digitalmars-d wrote:
 On Friday, 29 October 2021 at 21:56:10 UTC, Adam Ruppe wrote:
 On Friday, 29 October 2021 at 21:16:49 UTC, H. S. Teoh wrote:
 Long story short, pureFree makes no sense
What about: void foo() pure { int* a = malloc(5); scope(exit) free(a); }
foo is pure, but malloc and free aren't individually. If the declaration of the purity of foo is curtailed because malloc and free have to be marked as pure, then it is a failure of the language. In fact there should be an equivalent of trusted for purity, telling the compiler "trust me, I know that that combination of impure functions is on a whole pure". Marking malloc/free as pure doesn't cut it as these functions cannot, by definition, be pure individually.
Exactly, we need an escape hatch to tell the compiler "this sequence of operations are individually impure, but as a whole they are pure". Just like a sequence of pointer arithmetic operations are individually unsafe, but if properly constructed they may, as a whole, be safe, so we have trusted for that purpose.
 But I start to understand where this abomination of purity 
 comes from. Transitivity. I suspect that applying transitivity 
 unthinkingly is not such a good idea as can be seen also with 
 const (immutable is transitive as it describes a property of 
 the data, const is not as it is a property of the means to 
 access the data, not the data itself).
But how else can pure be implemented? If we remove transitivity from pure, then it essentially becomes an anemic recommendation for programming by convention, rather than something enforceable by the compiler. Sometimes an escape hatch is needed, but that doesn't mean the rest of the time we can just get away with whatever we want.
Yes, pure should be transitive (+- escape hatch). What I meant by my "unthinkingly" was that in that case, the transitivity requirement pushed the author to do the wrong thing and mark malloc/free as pure. This would have not been that severe if these functions had remained private and not leaked out of the englobing purity scope. The primordial sin here is that the symbols, with their abominable purity, escaped of its locality.
Nov 02 2021
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Nov 02, 2021 at 08:25:21PM +0000, Patrick Schluter via Digitalmars-d
wrote:
 On Tuesday, 2 November 2021 at 18:18:56 UTC, H. S. Teoh wrote:
 On Sat, Oct 30, 2021 at 10:17:56AM +0000, Patrick Schluter via
 Digitalmars-d wrote:
[...]
 But I start to understand where this abomination of purity comes
 from. Transitivity. I suspect that applying transitivity
 unthinkingly is not such a good idea as can be seen also with
 const [...].
But how else can pure be implemented? If we remove transitivity from pure, then it essentially becomes an anemic recommendation for programming by convention, rather than something enforceable by the compiler. Sometimes an escape hatch is needed, but that doesn't mean the rest of the time we can just get away with whatever we want.
Yes, pure should be transitive (+- escape hatch). What I meant by my "unthinkingly" was that in that case, the transitivity requirement pushed the author to do the wrong thing and mark malloc/free as pure. This would have not been that severe if these functions had remained private and not leaked out of the englobing purity scope. The primordial sin here is that the symbols, with their abominable purity, escaped of its locality.
These functions (malloc/free) should not have been marked pure in the first place. Even the more they should not have been made *public*, or made it into the standard library. pureMalloc/pureFree are abominations that ought to be killed with fire and extreme prejudice. Instead, there should have been an escape hatch that the *caller* of malloc/free should have used to indicate that it is pure. The compiler cannot statically verify this, so it must be the purity equivalent of trusted: the compiler just has to take the programmer's word for it. When pure is transitive (a good thing) but there's no escape hatch (a bad thing), the result is abominations like pureFree. T -- Programming is not just an act of telling a computer what to do: it is also an act of telling other programmers what you wished the computer to do. Both are important, and the latter deserves care. -- Andrew Morton
Nov 02 2021
prev sibling next sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Friday, 29 October 2021 at 21:56:10 UTC, Adam Ruppe wrote:
 On Friday, 29 October 2021 at 21:16:49 UTC, H. S. Teoh wrote:
 Long story short, pureFree makes no sense
What about: void foo() pure { int* a = malloc(5); scope(exit) free(a); } How is that any different than void foo() pure { int[5] a; } ?
That depends on how malloc and free are implemented... Should you for instance be allowed to do locking in a pure function? Probably not. You cannot call such code in a real time thread. If you cannot call a pure function in a real time thread then I think the advantage is completely lost for system level programming. The problem here is having a good definition for pure.
Oct 30 2021
prev sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Friday, 29 October 2021 at 21:56:10 UTC, Adam Ruppe wrote:
 On Friday, 29 October 2021 at 21:16:49 UTC, H. S. Teoh wrote:
 Long story short, pureFree makes no sense
What about: void foo() pure { int* a = malloc(5); scope(exit) free(a); } How is that any different than void foo() pure { int[5] a; } ?
That depends on how malloc and free are implemented... If malloc involves locking or system calls (which is difficult to avoid) then the difference is that it cannot be used in real time or other low level code where neither locking or system calls can be used. The problem here is having a useful definition for pure. What is the purpose for "pure"? With no clear purpose it becomes rather difficult to pinpoint what the boundary ought to be.
Oct 30 2021
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 29 October 2021 at 21:16:49 UTC, H. S. Teoh wrote:
 Array operations are OK if we view them as intrinsic, opaque 
 operations that the pure abstraction grants us.
My point is that in a systems-level language like D, where the side effects of memory allocation are directly observable from normal code [1][2], operations that allocate memory are *not* opaque, pure abstractions, and cannot possibly be "viewed" as such. I understand that, historically, this kind of argument has (successfully) been used to justify things like `pureMalloc` and pure array concatenation, but the argument is based on a false premise, and always has been. [1] https://dlang.org/phobos/core_memory.html#.GC.stats [2] https://dlang.org/phobos/core_memory.html#.GC.query
Oct 29 2021
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Oct 30, 2021 at 12:29:00AM +0000, Paul Backus via Digitalmars-d wrote:
 On Friday, 29 October 2021 at 21:16:49 UTC, H. S. Teoh wrote:
 Array operations are OK if we view them as intrinsic, opaque
 operations that the pure abstraction grants us.
My point is that in a systems-level language like D, where the side effects of memory allocation are directly observable from normal code [1][2], operations that allocate memory are *not* opaque, pure abstractions, and cannot possibly be "viewed" as such.
It can be, if such operations cannot be marked pure (they are certainly not pure). I.e., if code marked `pure` is restricted to a subset of operations, then it can be consistently optimized based on purity assumptions. It's the same idea as safe. D as a whole cannot be considered safe due to operations like pointer arithmetic and untagged unions, but code restricted to the safe subset can.
 I understand that, historically, this kind of argument has
 (successfully) been used to justify things like `pureMalloc` and pure
 array concatenation, but the argument is based on a false premise, and
 always has been.
 
 [1] https://dlang.org/phobos/core_memory.html#.GC.stats
 [2] https://dlang.org/phobos/core_memory.html#.GC.query
GC.stats / GC.query are impure operations, as are memory (de)allocation operations, and should not be allowed in pure code. T -- People say I'm indecisive, but I'm not sure about that. -- YHL, CONLANG
Oct 29 2021
prev sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Wednesday, 27 October 2021 at 19:53:52 UTC, H. S. Teoh wrote:
 The bigger benefit is the mechanical verification that you 
 didn't accidentally depend on global state when you didn't want 
 to, which can help with improving code quality.
It could if "pure" was default and you had to type "uses_globals" if you went past that. Adding pure all over the place is just too tedious and visually annoying.
Oct 28 2021
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Oct 27, 2021 at 07:19:31PM +0000, Dukc via Digitalmars-d wrote:
[...]
 ```d
 pure short coming()
 { typeof(return) result = void;
   return result; //may return anything
 }
 ```
 
 But when you think of it, why should this be a problem? This one
 returns an implementation defined value. If a compiler skips repeated
 calls to this one because of the `pure` attribute and just reuses the
 value from the first call, so what? Because the return values are
 implementation defined, the compiler is free to have them all to be
 the same with regards to each other.
Returning a void-initialized (i.e., *un*initialized) result invokes UB. The compiler is free to do (or not do) whatever it wants in this case. T -- Only boring people get bored. -- JM
Oct 27 2021
parent reply Dukc <ajieskola gmail.com> writes:
On Wednesday, 27 October 2021 at 19:31:52 UTC, H. S. Teoh wrote:
 Returning a void-initialized (i.e., *un*initialized) result 
 invokes UB.
 The compiler is free to do (or not do) whatever it wants in 
 this case.


 T
Only if the result contains pointers. With other types, the result is undefined but not the rest of the program.
Oct 27 2021
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Oct 27, 2021 at 07:40:10PM +0000, Dukc via Digitalmars-d wrote:
 On Wednesday, 27 October 2021 at 19:31:52 UTC, H. S. Teoh wrote:
 
 Returning a void-initialized (i.e., *un*initialized) result invokes
 UB.  The compiler is free to do (or not do) whatever it wants in
 this case.
[...]
 Only if the result contains pointers. With other types, the result is
 undefined but not the rest of the program.
Right, but the result being undefined means the compiler is free to cache or not cache the return value without violating the spec. T -- "Holy war is an oxymoron." -- Lazarus Long
Oct 27 2021
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Wednesday, 27 October 2021 at 19:40:10 UTC, Dukc wrote:
 On Wednesday, 27 October 2021 at 19:31:52 UTC, H. S. Teoh wrote:
 Returning a void-initialized (i.e., *un*initialized) result 
 invokes UB.
 The compiler is free to do (or not do) whatever it wants in 
 this case.


 T
Only if the result contains pointers. With other types, the result is undefined but not the rest of the program.
According to the current language spec, the entire program has undefined behavior whether or not the result contains pointers. Walter has proposed to change this [1], but the proposal has not been accepted. [1]: https://github.com/dlang/dlang.org/pull/2260
Oct 28 2021
parent Dukc <ajieskola gmail.com> writes:
On Thursday, 28 October 2021 at 14:49:41 UTC, Paul Backus wrote:
 According to the current language spec, the entire program has 
 undefined behavior whether or not the result contains pointers. 
 Walter has proposed to change this [1], but the proposal has 
 not been accepted.

 [1]: https://github.com/dlang/dlang.org/pull/2260
Some other change has been made to the spec after that PR was made. It currently reads:
 2. Implementation Defined: If a void initialized variable's 
 value is used before it is set, its value is implementation 
 defined.
 ```d
 void bad()
 {
     int x = void;
     writeln(x);  // print implementation defined value
 }
 ```
 3. Undefined Behavior: If a void initialized variable's value 
 is used before it is set, and the value is a reference, pointer 
 or an instance of a struct with an invariant, the behavior is 
 undefined.
 ```d
 void muchWorse()
 {
     char[] p = void;
     writeln(p);  // may result in apocalypse
 }
 ```
Oct 28 2021
prev sibling parent reply Dennis <dkorpel gmail.com> writes:
On Monday, 25 October 2021 at 13:42:19 UTC, Guillaume Piolat 
wrote:
 If you could make any changes to D, what would they look like?
It's a great thing other people get to decide, because I would remove: - pure - shared - property - inout - GC-calling-destructors - virtual-by-default - real - synchronized - inout - alias this - and parts of Phobos too. But D is also about catering to many different people.
Apart from `pure`, I agree with everything on that list, and would even add a bunch of things: - Module constructors - `lazy` parameters - `in` / `out` parameters - `in` / `out` contracts - Function body literals (`{}` instead of `() {}`) - Typesafe Variadic Functions (`foo(int[] a...)`) - String literals starting with `q` - The remaining octal literals (00-07) - `is()` expressions - `switch` with run-time variable cases - `switch` with string cases - `alias` reassignment - `__traits(compiles)` - `opDispatch` - `opCall` - `opApply`
Oct 25 2021
next sibling parent Guillaume Piolat <first.last gmail.com> writes:
On Monday, 25 October 2021 at 14:52:55 UTC, Dennis wrote:
 - Module constructors
 - `lazy` parameters
 - `in` / `out` parameters
 - `in` / `out` contracts
 - Function body literals (`{}` instead of `() {}`)
 - Typesafe Variadic Functions (`foo(int[] a...)`)
 - String literals starting with `q`
 - The remaining octal literals (00-07)
 - `is()` expressions
 - `switch` with run-time variable cases
 - `switch` with string cases
 - `alias` reassignment
 - `__traits(compiles)`
 - `opDispatch`
 - `opCall`
 - `opApply`
You agree with 9 of my 10 proposals. I agree with only about 10 of your 16 proposals. It's easy to see why this is a bit hard to remove things in practice. :)
Oct 25 2021
prev sibling next sibling parent reply Adam D Ruppe <destructionator gmail.com> writes:
On Monday, 25 October 2021 at 14:52:55 UTC, Dennis wrote:
 Apart from `pure`, I agree with everything on that list, and 
 would even add a bunch of things:
Damn, like do you actually use *any* D features?
Oct 25 2021
parent reply Dennis <dkorpel gmail.com> writes:
On Monday, 25 October 2021 at 15:59:35 UTC, Adam D Ruppe wrote:
 On Monday, 25 October 2021 at 14:52:55 UTC, Dennis wrote:
 Apart from `pure`, I agree with everything on that list, and 
 would even add a bunch of things:
Damn, like do you actually use *any* D features?
I know right? I'm starting to think maybe I should switch to Zig :p I have used some of these features, but then they ended up being annoying or unnecessary anyway: - Module constructors make it hard to see what's happening when the program starts. I once submitted a D program for an assignment and it failed because it loaded local files for unittests in a module constructor, which I forgot to remove because I only looked at main. - opDispatch: suddenly member introspection (`__traits(hasMember)`) succeeds when you don't expect it, so now it's accepted as a broken InputRange/OutputRange/whatever in generic functions. - alias this: I use it, I encounter [bugs](https://issues.dlang.org/show_bug.cgi?id=19477) and weird consequences like unexpected endless recursion, I remove it. It's kind of like your quote from Discord:
 so many times a questions comes up in learn like "how would i 
 do this with metaprogramming" and im like "bro just use a 
 standard function like you would in javascript"
Nowadays whenever I can express something with plain old structs/functions/arrays/enums, I do that instead of anything fancy, and I'm liking the result.
Oct 25 2021
next sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Monday, 25 October 2021 at 17:23:01 UTC, Dennis wrote:
 - Module constructors make it hard to see what's happening when 
 the program starts. I once submitted a D program for an 
 assignment and it failed because it loaded local files for 
 unittests in a module constructor, which I forgot to remove 
 because I only looked at main.
But I think you need it if you want to create module-specific allocators? Otherwise users of the module have to remember to add initializers and destructors externally. To prove that at compile time takes a very complex type system, I think? And checking it at runtime has a performance impact (unless you do runtime patching, which gets ugly real fast).
Oct 25 2021
prev sibling next sibling parent Adam D Ruppe <destructionator gmail.com> writes:
On Monday, 25 October 2021 at 17:23:01 UTC, Dennis wrote:
 - opDispatch: suddenly member introspection 
 (`__traits(hasMember)`) succeeds when you don't expect it
Yea, I kinda wish it didn't affect hasMember. This is one place where I pretty consistently use a template constraint to at least prohibit "popFront" in there. Hacky i know. My rule lately has also been opDispatch goes on its own thing. Like dom.d used to do element.attribute through opDispatch. Now it only allows it through element.attrs.attribute to help to contain it. I still think it is cool tho lol
 Nowadays whenever I can express something with plain old 
 structs/functions/arrays/enums, I do that instead of anything 
 fancy, and I'm liking the result.
indeed, generally good strats.
Oct 25 2021
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Oct 25, 2021 at 05:23:01PM +0000, Dennis via Digitalmars-d wrote:
[...]
 - Module constructors make it hard to see what's happening when the
   program starts. I once submitted a D program for an assignment and
   it failed because it loaded local files for unittests in a module
   constructor, which I forgot to remove because I only looked at main.
But that's hardly the fault of module ctors. You're not supposed to do unittest-related stuff outside of unittest blocks and stuff versioned by `version(unittest)`. What you *should* have done is: version(unittest) static this() { // load stuff you need for unittests here } // regular module ctor (yes you can have multiple static ctors, // they just get concatenated together -- this is actually a // feature). static this() { // DON'T do stuff here that's unittest-related! }
 - opDispatch: suddenly member introspection (`__traits(hasMember)`)
 succeeds when you don't expect it, so now it's accepted as a broken
 InputRange/OutputRange/whatever in generic functions.
I suspect part of this problem is caused by the lame specialcased behaviour of template instantiation errors being ignored when it's opDispatch. It's D's version of C++'s SFINAE, which leads to all sorts of stupidity like a typo inside opDispatch suddenly causing a dispatched member to disappear from existence 'cos the compiler swallows the error and pretends the member simply doesn't exist. So if you're introspecting the member, the introspection code doesn't even see it. Which leads to hours of head-scratching over "I obviously declared the darned member, why does the code NOT see it?!?!". Errors in opDispatch seriously ought to be treated as errors. If some member isn't supposed to be part of the dispatch, it should be checked for in the sig constraint or something like that, instead of the compiler silently swallowing the error.
 - alias this: I use it, I encounter
 [bugs](https://issues.dlang.org/show_bug.cgi?id=19477) and weird
 consequences like unexpected endless recursion, I remove it.
Yeah `alias this` beyond the most trivial of usecases leads to nothing but pain.
 It's kind of like your quote from Discord:
 
 so many times a questions comes up in learn like "how would i do
 this with metaprogramming" and im like "bro just use a standard
 function like you would in javascript"
Nowadays whenever I can express something with plain old structs/functions/arrays/enums, I do that instead of anything fancy, and I'm liking the result.
Isn't this what we're supposed to be doing anyway? I mean, you can do *anything* with mixin(), but most of the time you shouldn't because there are much more readable and maintainable ways to accomplish the same thing. :-D T -- To err is human; to forgive is not our policy. -- Samuel Adler
Oct 25 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 25 October 2021 at 17:45:22 UTC, H. S. Teoh wrote:
 I suspect part of this problem is caused by the lame 
 specialcased behaviour of template instantiation errors being 
 ignored when it's opDispatch.  It's D's version of C++'s 
 SFINAE, which leads to all sorts of stupidity like a typo 
 inside opDispatch suddenly causing a dispatched member to 
 disappear from existence 'cos the compiler swallows the error 
 and pretends the member simply doesn't exist.  So if you're 
 introspecting the member, the introspection code doesn't even 
 see it. Which leads to hours of head-scratching over "I 
 obviously declared the darned member, why does the code NOT see 
 it?!?!".

 Errors in opDispatch seriously ought to be treated as errors. 
 If some member isn't supposed to be part of the dispatch, it 
 should be checked for in the sig constraint or something like 
 that, instead of the compiler silently swallowing the error.
It's actually worse than that. Errors inside `opDispatch` are gagged and cause member lookup to fail...*unless* they're nested inside *another* template, in which case member lookup will succeed, and you will only get the error when you try to actually access the member: ```d struct S1 { template opDispatch(string member) { // error is gagged by compiler static assert(0); } } // member lookup fails static assert(__traits(hasMember, S1, "foobar") == false); struct S2 { template opDispatch(string member) { // inner template auto opDispatch()() { // error is not gagged static assert(0); } } } // member lookup succeeds static assert(__traits(hasMember, S2, "foobar") == true); // ...but usage triggers the error auto _ = S2.init.foobar; ``` It's special cases inside of special cases--a complete mess.
Oct 25 2021
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Oct 25, 2021 at 06:06:27PM +0000, Paul Backus via Digitalmars-d wrote:
 On Monday, 25 October 2021 at 17:45:22 UTC, H. S. Teoh wrote:
 I suspect part of this problem is caused by the lame specialcased
 behaviour of template instantiation errors being ignored when it's
 opDispatch.  It's D's version of C++'s SFINAE, which leads to all
 sorts of stupidity like a typo inside opDispatch suddenly causing a
 dispatched member to disappear from existence 'cos the compiler
 swallows the error and pretends the member simply doesn't exist.  So
 if you're introspecting the member, the introspection code doesn't
 even see it.  Which leads to hours of head-scratching over "I
 obviously declared the darned member, why does the code NOT see
 it?!?!".
[...]
 It's actually worse than that. Errors inside `opDispatch` are gagged
 and cause member lookup to fail...*unless* they're nested inside
 *another* template, in which case member lookup will succeed, and you
 will only get the error when you try to actually access the member:
[...]
 It's special cases inside of special cases--a complete mess.
=-O Wow, that's totally messed up. :-/ Yeah, if there's any way to fix this without breaking existing code, I'd love to push for this change in the language. Not holding my breath for it, though, unfortunately. T -- Trying to define yourself is like trying to bite your own teeth. -- Alan Watts
Oct 25 2021
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Oct 25, 2021 at 02:52:55PM +0000, Dennis via Digitalmars-d wrote:
[...]
 - Module constructors
Module ctors are da bomb! You can do all sorts of awesome things with them, if you know how to combine them with compile-time introspection and other metaprogramming techniques. It's part of why Adam's jni.d module is so awesome.
 - `lazy` parameters
I rarely need this, so meh. I guess it's nice to have when you do need it, but so far I haven't.
 - `in` / `out` parameters
IMO these are very useful.
 - `in` / `out` contracts
These too, except the implementation could be improved.
 - Function body literals (`{}` instead of `() {}`)
One can argue about syntax till the cows come home, and we wouldn't come to an agreement. :D
 - Typesafe Variadic Functions (`foo(int[] a...)`)
These are awesome! Lets you construct array arguments on the stack where a function accepting only an array would force a GC (or other) allocation.
 - String literals starting with `q`
Heredoc literals are awesome, I use them all the time. They make code-generating code actually readable, instead of being candidate submissions for the IOCCC. :-/ Though I can see how the proliferation of different string literals make the D spec somewhat unwieldy...
 - The remaining octal literals (00-07)
Yeah, kill octals with fire and extreme prejudice. :->
 - `is()` expressions
Yeah they are a mess. Well, not so bad once you understand the logic behind them, but still, they're very opaque for a newcomer. And there's also the odd hidden dark corner that will make even a seasoned D veteran cringe, e.g.: https://forum.dlang.org/thread/vpjpqfiqxkmeavtxhyla forum.dlang.org But is() expressions are also the backbone of much of D's metaprogramming prowess, so unless you come up with something better, they are here to stay.
 - `switch` with run-time variable cases
Yeah... I generally avoid using switches in that way. It just makes code hard to read.
 - `switch` with string cases
This is actually nice for some cases. But the current implementation involves instantiating a template potentially with a huge number of arguments (*ahem*cough*std.datetime*ahem*), which at one point caused so much grief we had to rewrite either std.datetime or the druntime implementation of the string switch, I forget which.
 - `alias` reassignment
Whaddya mean, that's one of the best things to happen to D lately, that gets rid of a whole bunch of evil recursive templates from druntime and Phobos.
 - `__traits(compiles)`
Yeah, this one's a loaded gun. It seems a good idea on the surface, until you realize that most (all?) of the time, you actually don't *mean* "does this code compile"; usually you have something much narrower in scope like "does X have a member named Y" or "is X callable with Y as argument". Using __traits(compiles) for this quickly leads to the problem where you *can* call X with Y, or X does have a member named Y, etc., but there's a typo somewhere in the code that makes it non-compilable, so suddenly your template overload resolution goes in a completely unexpected direction generating screenfuls of completely unhelpful and irrelevant errors.
 - `opDispatch`
opDispatch is awesome. See: Adam's jni.d.
 - `opCall`
opCall is needed for function objects. How else are you supposed to reify functions when you need to?!
 - `opApply`
This is actually useful in cases where the range API may not be the best way to do things (believe it or not, there *are* such cases). One example is iterating over a tree without needing to allocate more memory for the iteration. T -- My program has no bugs! Only undocumented features...
Oct 25 2021
next sibling parent Adam D Ruppe <destructionator gmail.com> writes:
On Monday, 25 October 2021 at 16:31:36 UTC, H. S. Teoh wrote:
 Whaddya mean, that's one of the best things to happen to D 
 lately, that gets rid of a whole bunch of evil recursive 
 templates from druntime and Phobos.
lol it is pretty lame really.
 opDispatch is awesome. See: Adam's jni.d.
I don't think I use it in jni since that's all generated. But jsvar and dom both use it pretty heavily and i like it. (but I do wish the error message regression would be fixed)
Oct 25 2021
prev sibling parent Dennis <dkorpel gmail.com> writes:
On Monday, 25 October 2021 at 16:31:36 UTC, H. S. Teoh wrote:
 - Function body literals (`{}` instead of `() {}`)
One can argue about syntax till the cows come home, and we wouldn't come to an agreement. :D
I don't mind the look of it that much, but it's super ambiguous. The current parser has a hack to distinguish them from struct initializers, and fails to parse it in places where it the grammar allows it e.g. [Issue 14378](https://forum.dlang.org/reply/mailman.357.1635179501.11670.digitalmars-d puremagic.com).
 These are awesome!  Lets you construct array arguments on the 
 stack where a function accepting only an array would force a GC 
 (or other) allocation.
You can pass an array literal to a `scope int[]` parameter ` nogc`.
 But is() expressions are also the backbone of much of D's 
 metaprogramming prowess, so unless you come up with something 
 better, they are here to stay.
Yeah, they'd need a replacement, but man they are so hard to use currently. Was it `is(T == E[], E)` or `is(E[] == T, E)`? Why does `E` become in-scope when the `is()` is in a `static if`, but not in a template constraint? Why is `function` in an `is()` expression a function type while elsewhere it's a function pointer type?
 - `alias` reassignment
Whaddya mean, that's one of the best things to happen to D lately, that gets rid of a whole bunch of evil recursive templates from druntime and Phobos.
That could be done in other ways, without introducing AST mutation.
 - `opCall`
opCall is needed for function objects. How else are you supposed to reify functions when you need to?!
I don't know, I never made custom function types.
 - `opApply`
This is actually useful in cases where the range API may not be the best way to do things (believe it or not, there *are* such cases).
You can still call an `opApply`-like function directly without `foreach`.
 One example is iterating over a tree without needing to 
 allocate more memory for the iteration.
I tend to use recursion for that, since my trees don't get that deep.
Oct 25 2021
prev sibling next sibling parent reply Dukc <ajieskola gmail.com> writes:
On Wednesday, 20 October 2021 at 09:47:54 UTC, SealabJaster wrote:
 Just for giggles, without pesky things like breaking changes; 
 rational thinking, logical reasoning behind the changes, etc.

 What interesting changes would you make to the language, and 
 what could they possibly look like?
Forgetting "rational thinking, logical reasoning behind the changes" - how? Isn't it all rational reasoning in programming language design - if we leave it out, what's left? I don't know what you meant, so rest of my reply just ignores that part. The answer is, probably not too much after all. If I really want to change something, I can write a DIP. So far I've written only one. Now of course, if I could forget about breaking changes, I could propose things I wouldn't otherwise. And I think you meant I could do the changes without bothering with the DIPs, so I would have more power than I do. But still - I think the DIP process keeps me honest on what I really am ready to bother on, so I don't think I would change that much stuff even if I could just write a spec piece and have it automatically accepted. Now, were I a full-time BDFL so that I would spend all day on the question, some changes I might do: - Revise ` safe` by default DIP so that ` safe` foreign language declarations would deprecated but not immediately removed. One more review and then accept it unless a major flaw is found. - Write a DIP: short integer types auto-promoted to `int`/`uint` are implicitly convertible back to original type within the same expression. I've been toying with the idea. - Accept Dennis's unit type DIP when it hits the the formal assessment. - Accept `preview=shortenedMethods` DIP when it hits the the formal assessment. - Write a DIP for `preview=in` and accept it unless a major flaw is found (yet I haven't wrote one IRL). - Phobos should strive to be more decoupled from the compiler and runtime. Using internal DRuntime features should not be allowed, and the compiler should be able to compile older Phobos releases (Haven't tested if this is the case TBH). - There's a dead letter in the spec that says that `(x) trusted => {/*...*/}` pattern is allowed only if the lambda is safe regardless of the value of the argument and the surrounding context. I'd remove that rule. - Move the ImportC section from the spec to the DMD manual. - Declaration against the `version(Something) /*...*/ else static assert(0, "not supported");` pattern in Phobos and DRuntime. Just compile what is implemented and leave other stuff out, instead of failing compilation. - Avoid adding anything major into language without a DIP. I would allow myself to write it if no-one volunteers to champion the DIP. Well, It turns out I didn't shake the question of backwards compatibility after all. I guess it's because I want to consider it - backwards compatibility is part of my D dream.
Oct 25 2021
next sibling parent Dukc <ajieskola gmail.com> writes:
On Monday, 25 October 2021 at 16:04:12 UTC, Dukc wrote:
 - There's a dead letter in the spec that says that `(x) 
  trusted => {/*...*/}` pattern is allowed only if the lambda is 
 safe regardless of the value of the argument and the 
 surrounding context.
Meant `(x) trusted {/*...*/}`
Oct 25 2021
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Oct 25, 2021 at 04:04:12PM +0000, Dukc via Digitalmars-d wrote:
[...]
 backwards compatibility is part of my D dream.
Sometimes I wonder about writing a DIP for declaring language version at the top of a module to (almost) guarantee backward compatibility. But realistically speaking, that would mean a whole ton more of work for the already-scarce current compiler maintainers, so this is probably unlikely to ever happen. But one can dream... T -- Without outlines, life would be pointless.
Oct 25 2021
prev sibling next sibling parent victoroak <jackpboy gmail.com> writes:
On Wednesday, 20 October 2021 at 09:47:54 UTC, SealabJaster wrote:
 Just for giggles, without pesky things like breaking changes; 
 rational thinking, logical reasoning behind the changes, etc.

 What interesting changes would you make to the language, and 
 what could they possibly look like?
For me the top priority would be to make what D already has more robust and fix some really annoying bugs about delegates and member alias parameters like issue https://issues.dlang.org/show_bug.cgi?id=5710) that was fixed and then reverted. There are a lot of other "features" that are half finished or in "preview" and "experimental" forever that could be made stable too. Sure, there are some features (e.g. tuples and destructuring) that I really want but ironing out what is already there is way more important for me. The issue I linked before was one that I discovered like 5m after trying out D and still bugs me out to this day.
Oct 25 2021
prev sibling next sibling parent Bienlein <jeti789 web.de> writes:
Remove the problems that make the GC slow. I know this cannot be 
done without breaking many things, but this topic is about 
expressing wishes, right? ;-)
Oct 27 2021
prev sibling parent harakim <harakim gmail.com> writes:
On Wednesday, 20 October 2021 at 09:47:54 UTC, SealabJaster wrote:
 Just for giggles, without pesky things like breaking changes; 
 rational thinking, logical reasoning behind the changes, etc.

 What interesting changes would you make to the language, and 
 what could they possibly look like?

 Here's a small example of some things I'd like.

 ```d
 import std;

 interface Animal
 {
     void speak(string language);
 }

 struct Dog
 {
      nogc  nothrow  pure  safe
     static void speak(string l)
     {
         // Pattern matching of some kind
         // With strings this is just a fancy switch statement, 
 but this is the gist of it
         match l with
         {
             "english" => writeln("woof"),
             "french" => writeln("le woof"),
             _ => writeln("foow")
         }
     }
 }

 struct Cat
 {
     // Remove historical baggage. Make old attributes into ` ` 
 attributes
      nogc  nothrow  pure  safe
     static void speak()
     {
         writeln("meow")
     }
 }

 // ? for "explicitly nullable"
 void doSpeak(alias T)(string? language)
 if(is(T == struct) && match(T : Animal)) // Match structs 
 against an interface.
 {

     auto lang = language ?? "UNKNOWN";

     // So of course we'd need a mutable keyword of some sort
     mutable output = $"{__traits(identifier, T)} speaking in 
 {lang}"; // String interpolation
     writeln(output);
     T.speak(lang);
 }

 void main()
 {
     doSpeak!Dog;
     doSpeak!Cat; // Should be a compiler error since it fails 
 the `match` statement
 }
 ```
* I would probably choose a different syntax for templates. * I would add ?. and ?? * I would work on the levels of strictness. D definitely does this better than any other language, but I have used it and found it wanting from time to time. Most projects start as a prototype and you don't need a lot of strict rules for that. For that you don't want type checking or method signature validation, you want a scripting language. As you get going, you want to still move fast but you probably want a strongly-typed language and some other features. At some point, certain parts of the code, libraries or even whole programs can be battened down and you want features like compile-time guarantees and performance. I would say D is the leader in this for all the languages that I use, but I think it could have a little more room to grow and have better documentation. * Possibly add something to help autocomplete. For example, in advantage they have over normal methods.
Oct 30 2021