www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Compile time values & implicit conditional mixin, as an alternative to

reply Paul <paultjeadriaanse gmail.com> writes:
I've gotten slightly tangled up with tertiary operators inside 
mixins, and it feels like the language is actively working 
against me, mostly due to the inability to create compile time 
only variable and the removal of comma operators.
In some sense, this makes compile time evaluation in D feels 
slightly incomplete / rough, which feels like a waste as its 
potential seems very powerful to me.

Consider the following excerpts:

 enum string values= "value.x" ~ (L == 1 ? "" : ",value.y" ~ (L 
 == 2 ? "" : ",value.z" ~ (L == 3 ? "" : ",value.w")));
 enum string kind = is(S == uint) ? "ui" :  (is(S == int) ? "i" 
 :  (is(S == float) ? "f" : (assert(is(S == double)), "d"))); 
 //(ignore the comma operator for now)
 mixin("glUniform" ~ L.to!string ~ kind ~ "(uniformlocation, " ~ 
 values ~ ");");
Noticing two things: 1. Due to this being at compile time, I'm trying to use enums. Sadly however, due to these being enums I cannot change them after their definition, and am thus forced to use tertiary operators (which feels problematic and very confusing) 2. Due to the comma operator being depricated (and not replaced?!) I can no longer "neatly" assert things inside the enum creation, and I see the possibility of needing another other than solely the double type assert, in which case adding an assert on the next line is no longer a valid option, given some 'special' value for the "kind" enum string would no longer be possible. (I would make a case for the comma operator only being renamed, in the exact same way one wouldn't ban goto, but that's beside this post) Ideally this would be solved with one of the following: - implicit conditional mixin, as an alternative to tertiary operator hell.
 enum string values = "value.x" (L >= 2 ?) ~ ",value.y" (L == 2 
 ?) ~ ",value.z" (L == 3 ?)~ ",value.w";
- compile time only variables (or functions*), as manifest constants cant be modified and normal variables are not useable inside mixins:
 compile string waardes = "waarde.x";
 static if(L>=2) waardes ~= ",waarde.y";
 static if(L>=3) waardes ~= ",waarde.z";
 static if(L==4) waardes ~= ",waarde.w";
I realized creating a seperate function to return a string callable inside mixin is an option. However creating a seperate function purely to do compile time string creation seems like a design flaw, which additionally does not prevent its usage at runtime. * (Hence the mention of compiletime only functions, although this feels like a partial fix on the actual issue) My dearest apology if this post is unhelpful, or if either of these two is actually possible! The expressive power of compile time evaluation just feels ever so slightly out of my grasp, and I'd like to know what opinion the rest of the community has regarding this topic.
Jan 15
parent reply Paul Backus <snarwin gmail.com> writes:
On Saturday, 16 January 2021 at 03:36:23 UTC, Paul wrote:
 Consider the following excerpts:

 enum string values= "value.x" ~ (L == 1 ? "" : ",value.y" ~ (L 
 == 2 ? "" : ",value.z" ~ (L == 3 ? "" : ",value.w")));
 enum string kind = is(S == uint) ? "ui" :  (is(S == int) ? "i" 
 :  (is(S == float) ? "f" : (assert(is(S == double)), "d"))); 
 //(ignore the comma operator for now)
 mixin("glUniform" ~ L.to!string ~ kind ~ "(uniformlocation, " 
 ~ values ~ ");");
Noticing two things: 1. Due to this being at compile time, I'm trying to use enums. Sadly however, due to these being enums I cannot change them after their definition, and am thus forced to use tertiary operators (which feels problematic and very confusing) 2. Due to the comma operator being depricated (and not replaced?!) I can no longer "neatly" assert things inside the enum creation, and I see the possibility of needing another other than solely the double type assert, in which case adding an assert on the next line is no longer a valid option, given some 'special' value for the "kind" enum string would no longer be possible. [...] I realized creating a seperate function to return a string callable inside mixin is an option. However creating a seperate function purely to do compile time string creation seems like a design flaw, which additionally does not prevent its usage at runtime.
You can use immediately-invoked function literals to work around all of these issues: enum string values = () { string result = "value.x"; if (L != 1) { result ~= ",value.y"; } if (L != 2) { result ~= ",value.z"; } if (L != 3) { result ~= ",value.w"; } return result; }(); // call the function we just defined enum string kind = () { if (is(S == uint)) { return "ui"; } else if (is(S == float)) { return "f"; } else { assert(is(S == double)); return "d"; } }();
Jan 15
parent reply Paul <paultjeadriaanse gmail.com> writes:
On Saturday, 16 January 2021 at 04:13:12 UTC, Paul Backus wrote:
 You can use immediately-invoked function literals to work 
 around all of these issues:

 enum string values = () {
     string result = "value.x";
     if (L != 1) { result ~= ",value.y"; }
     if (L != 2) { result ~= ",value.z"; }
     if (L != 3) { result ~= ",value.w"; }
     return result;
 }(); // call the function we just defined

 enum string kind = () {
     if (is(S == uint)) { return "ui"; }
     else if (is(S == float)) { return "f"; }
     else { assert(is(S == double)); return "d"; }
 }();
This seems like a workaround for defining a function, and seems semantically strange to me. You're still restricted to immediate local 'onelines'. Not that my issue requires non-oneliners, but were my use case to have to mix/match the string depending on compile time conditions, you'd have to create either many almost identical function, or ones with additional reoccuring conditions, instead of building a variable at compile time, with conditionals occuring only once. Thanks for the suggestion nontheless, that may be more organized for my current (rougly simple, I may need more complexity later on) case. 🙂
Jan 16
next sibling parent reply Sebastiaan Koppe <mail skoppe.eu> writes:
On Saturday, 16 January 2021 at 14:20:19 UTC, Paul wrote:
 On Saturday, 16 January 2021 at 04:13:12 UTC, Paul Backus wrote:
 You can use immediately-invoked function literals to work 
 around all of these issues:

 enum string values = () {
     string result = "value.x";
     if (L != 1) { result ~= ",value.y"; }
     if (L != 2) { result ~= ",value.z"; }
     if (L != 3) { result ~= ",value.w"; }
     return result;
 }(); // call the function we just defined

 enum string kind = () {
     if (is(S == uint)) { return "ui"; }
     else if (is(S == float)) { return "f"; }
     else { assert(is(S == double)); return "d"; }
 }();
This seems like a workaround for defining a function, and seems semantically strange to me. You're still restricted to immediate local 'onelines'. Not that my issue requires non-oneliners, but were my use case to have to mix/match the string depending on compile time conditions, you'd have to create either many almost identical function, or ones with additional reoccuring conditions, instead of building a variable at compile time, with conditionals occuring only once. Thanks for the suggestion nontheless, that may be more organized for my current (rougly simple, I may need more complexity later on) case. 🙂
you can also use CTFE. Generate the values in a normal function, assign it to an enum. --- string generateValues(uint L)() { string result = "value.x"; if (L != 1) { result ~= ",value.y"; } if (L != 2) { result ~= ",value.z"; } if (L != 3) { result ~= ",value.w"; } return result; } enum string values = generateValues!3(); pragma(msg, values); void main(){}; ---
Jan 16
parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Saturday, 16 January 2021 at 14:56:18 UTC, Sebastiaan Koppe 
wrote:
 On Saturday, 16 January 2021 at 14:20:19 UTC, Paul wrote:
 On Saturday, 16 January 2021 at 04:13:12 UTC, Paul Backus 
 wrote:
you can also use CTFE. Generate the values in a normal function, assign it to an enum. --- string generateValues(uint L)() { string result = "value.x"; if (L != 1) { result ~= ",value.y"; } if (L != 2) { result ~= ",value.z"; } if (L != 3) { result ~= ",value.w"; } return result; } enum string values = generateValues!3(); pragma(msg, values); void main(){}; ---
That's not a good pattern string generateValues(uint L)() should be string generateValues(uint L) and the invocation: enum string values = generateValues!3(); should be enum string values = generateValues(3); What you are doing here is an expensive template instantiation for no reason.
Jan 16
parent Sebastiaan Koppe <mail skoppe.eu> writes:
On Saturday, 16 January 2021 at 15:17:04 UTC, Stefan Koch wrote:
 On Saturday, 16 January 2021 at 14:56:18 UTC, Sebastiaan Koppe 
 wrote:
 On Saturday, 16 January 2021 at 14:20:19 UTC, Paul wrote:
 On Saturday, 16 January 2021 at 04:13:12 UTC, Paul Backus 
 wrote:
you can also use CTFE. Generate the values in a normal function, assign it to an enum. --- string generateValues(uint L)() { string result = "value.x"; if (L != 1) { result ~= ",value.y"; } if (L != 2) { result ~= ",value.z"; } if (L != 3) { result ~= ",value.w"; } return result; } enum string values = generateValues!3(); pragma(msg, values); void main(){}; ---
What you are doing here is an expensive template instantiation for no reason.
Yes thanks. I translated the thing without thinking too much.
Jan 16
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Saturday, 16 January 2021 at 14:20:19 UTC, Paul wrote:
 This seems like a workaround for defining a function, and seems 
 semantically strange to me. You're still restricted to 
 immediate local 'onelines'.
This is true no matter what you do, because `enum` constants are immutable. Their value has to be computed in a single expression, and once assigned, it can never change.
Jan 16
parent reply Paul <paultjeadriaanse gmail.com> writes:
On Saturday, 16 January 2021 at 16:23:18 UTC, Paul Backus wrote:
 This is true no matter what you do, because `enum` constants 
 are immutable. Their value has to be computed in a single 
 expression, and once assigned, it can never change.
Wouldn't it make sense to also have a mutable compile time variable though? I don't know about implimentation etc, but semantically I find it strange there isn't. (Instead of running 'normal' code to retrieve a value from it to be assigned to an enum constant since it couldnt be made from the get go)
Jan 16
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 17 January 2021 at 00:28:02 UTC, Paul wrote:
 On Saturday, 16 January 2021 at 16:23:18 UTC, Paul Backus wrote:
 This is true no matter what you do, because `enum` constants 
 are immutable. Their value has to be computed in a single 
 expression, and once assigned, it can never change.
Wouldn't it make sense to also have a mutable compile time variable though? I don't know about implimentation etc, but semantically I find it strange there isn't. (Instead of running 'normal' code to retrieve a value from it to be assigned to an enum constant since it couldnt be made from the get go)
Why add a dedicated new language feature if you can get the same behavior "for free" using regular old functions together with CTFE? D is already a pretty complex language, after all.
Jan 16
parent reply Paul <paultjeadriaanse gmail.com> writes:
On Sunday, 17 January 2021 at 01:51:21 UTC, Paul Backus wrote:
 Why add a dedicated new language feature if you can get the 
 same behavior "for free" using regular old functions together 
 with CTFE? D is already a pretty complex language, after all.
Because the alternative seems more complex to me. It could be a lot more consistent as well, the way the keyword 'static' is used at the moment seems very inconsistent to me, the same could be said for manifest constants I think. (Referring to static if, static foreach, static assert instead of static attributes) Having a single keyword for all, with added expressiveness, seems a lot clearner & simpler to me.
Jan 17
parent reply Paul <paultjeadriaanse gmail.com> writes:
On Sunday, 17 January 2021 at 15:59:31 UTC, Paul wrote:

 - compile time only variables (or functions*), as manifest 
 constants cant be modified and normal variables are not useable 
 inside mixins:
 compile string waardes = "waarde.x";
 static if(L>=2) waardes ~= ",waarde.y";
 static if(L>=3) waardes ~= ",waarde.z";
 static if(L==4) waardes ~= ",waarde.w";
&
 On Sunday, 17 January 2021 at 01:51:21 UTC, Paul Backus wrote:
 Why add a dedicated new language feature if you can get the 
 same behavior "for free" using regular old functions together 
 with CTFE? D is already a pretty complex language, after all.
Because the alternative seems more complex to me. It could be a lot more consistent as well, the way the keyword 'static' is used at the moment seems very inconsistent to me, the same could be said for manifest constants I think. (Referring to static if, static foreach, static assert instead of static attributes) Having a single keyword for all, with added expressiveness, seems a lot clearner & simpler to me.
If I wanted to make this into a serious language suggestion, how should I? Does this require a DIP, or does it make sense to first start a discussion post solely about this topic? Am I even being realistic here, I have no idea ":\
Jan 20
parent reply Paul Backus <snarwin gmail.com> writes:
On Wednesday, 20 January 2021 at 14:43:15 UTC, Paul wrote:
 If I wanted to make this into a serious language suggestion, 
 how should I?
 Does this require a DIP, or does it make sense to first start a 
 discussion post solely about this topic? Am I even being 
 realistic here, I have no idea ":\
A DIP would be required before this feature could be officially added to the language, but generally it makes sense to discuss the idea with the community before writing a DIP. In this case, I believe it is very unlikely that such a DIP would be accepted because: 1. The problem it aims to solve can already be solved by existing language features (CTFE). 2. It adds a new keyword to the language.
Jan 20
next sibling parent Paul <paultjeadriaanse gmail.com> writes:
On Wednesday, 20 January 2021 at 15:40:50 UTC, Paul Backus wrote:
 I believe it is very unlikely that such a DIP would be accepted 
 because:

 1. The problem it aims to solve can already be solved by 
 existing language features (CTFE).
 2. It adds a new keyword to the language.
It does seem to me that, were 'switch' to be a new feature, these points would hold as well, but I'll take your word for it 😅. However I do think having a new/seperate keyword for a concept that's already abusing the 'static' keyword would make more sense. (Especially for people that have not used C)
Jan 20
prev sibling parent sighoya <sighoya gmail.com> writes:
On Wednesday, 20 January 2021 at 15:40:50 UTC, Paul Backus wrote:
 On Wednesday, 20 January 2021 at 14:43:15 UTC, Paul wrote:
 If I wanted to make this into a serious language suggestion, 
 how should I?
 Does this require a DIP, or does it make sense to first start 
 a discussion post solely about this topic? Am I even being 
 realistic here, I have no idea ":\
A DIP would be required before this feature could be officially added to the language, but generally it makes sense to discuss the idea with the community before writing a DIP. In this case, I believe it is very unlikely that such a DIP would be accepted because: 1. The problem it aims to solve can already be solved by existing language features (CTFE). 2. It adds a new keyword to the language.
I often found myself limited to leverage recursion unnecessarily only because I want to reshadow an enum or static variable. Why isn't that possible, it hasn't to be mutation at all, only rebinding.
Jan 21
prev sibling parent reply Max Haughton <maxhaton gmail.com> writes:
On Sunday, 17 January 2021 at 00:28:02 UTC, Paul wrote:
 On Saturday, 16 January 2021 at 16:23:18 UTC, Paul Backus wrote:
 This is true no matter what you do, because `enum` constants 
 are immutable. Their value has to be computed in a single 
 expression, and once assigned, it can never change.
Wouldn't it make sense to also have a mutable compile time variable though? I don't know about implimentation etc, but semantically I find it strange there isn't. (Instead of running 'normal' code to retrieve a value from it to be assigned to an enum constant since it couldnt be made from the get go)
They would have the potential to make compiling really hard if they were allowed freely. If you imagine a mutable enum at module scope, it could be modified all over the place and lead to completely non-deterninistic compilation. Some mechanisms to (say) add to a table at compile time might be useful (you can fake this using ELF sections), but mutability is a footgun
Jan 16
parent Doug <doug_x_010123051385 hotmail.com> writes:
On Sunday, 17 January 2021 at 02:02:00 UTC, Max Haughton wrote:
 On Sunday, 17 January 2021 at 00:28:02 UTC, Paul wrote:
 [...]
They would have the potential to make compiling really hard if they were allowed freely. If you imagine a mutable enum at module scope, it could be modified all over the place and lead to completely non-deterninistic compilation. Some mechanisms to (say) add to a table at compile time might be useful (you can fake this using ELF sections), but mutability is a footgun
We're getting aliases that may be assigned multiple times before being read; so why not?
Jan 16