Jump to page: 1 2
Thread overview
Compile time values & implicit conditional mixin, as an alternative to tertiary operator hell and one-compile-time functions.
Jan 16, 2021
Paul
Jan 16, 2021
Paul Backus
Jan 16, 2021
Paul
Jan 16, 2021
Sebastiaan Koppe
Jan 16, 2021
Stefan Koch
Jan 16, 2021
Sebastiaan Koppe
Jan 16, 2021
Paul Backus
Jan 17, 2021
Paul
Jan 17, 2021
Paul Backus
Jan 17, 2021
Paul
Jan 20, 2021
Paul
Jan 20, 2021
Paul Backus
Jan 20, 2021
Paul
Jan 21, 2021
sighoya
Jan 17, 2021
Max Haughton
Jan 17, 2021
Doug
January 16, 2021
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.
January 16, 2021
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"; }
}();
January 16, 2021
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. 🙂
January 16, 2021
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(){};
---
January 16, 2021
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.

January 16, 2021
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.

January 16, 2021
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.
January 17, 2021
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)
January 17, 2021
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.
January 17, 2021
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
« First   ‹ Prev
1 2