www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Yet another terrible compile time argument proposal

reply Steven Schveighoffer <schveiguy gmail.com> writes:
On Friday, 12 January 2024 at 22:35:54 UTC, Walter Bright 
[wrote](https://forum.dlang.org/post/unseru$167e$1 digitalmars.com):
 Given the interest in CTFE of istrings, I have been thinking 
 about making a general use case out of it instead of one 
 specific to istrings.
Been also thinking about this and I have the following conclusions: - Walter's proposal seems super hacky and probably shouldn't be done. I don't like the idea of magically migrating an argument from one place to match a parameter in another, especially based on arbitrary rules that are constructed to fit the current use case (and in fact was shown not to actually help in the long run. - I really *really* like the idea of passing a simple struct at either runtime or compile time to a function over instrumented parameters (like the `InterpolationLiteral!"str"` or Rikki's attribute passing (which basically becomes extra hidden template parameters, that the function might not even use). - The syntax for any such mechanism should be obvious and straightforward. Instead of pushing runtime value arguments into compile time value parameters, why not just identify that they are compile time in the argument list? I like the [idea from Mathis Beer (FeepingCreature)](https://forum.dlang.org/post/wkwgipddfegevbrim av forum.dlang.org) that was linked by Nick earlier, but that also has some (I think) ambiguous syntax, and requires some coordination between separate parts. I'm going to propose something that furthers the baggage of `enum`, but that is the tool we currently have... Happy to think about better syntax. But why not just: ```d void foo(T)(T x, enum string s) { pragma(msg, s); // s is compile-time } void main(string[] args) { foo(1, "hi"); // ok foo(1, "hello"); // ok, but a different instantiation foo(1, args[1]); // error, second argument must be compile time } ``` Basically, an enum attributed parameter in the runtime parameter list requires a compile-time value, and implicitly adds it to the template arguments for the template instantiation. This is very similar to FeepingCreature's proposal, but without the extra ambiguity. There is no mechanism to explicitly pass the parameter in the compile-time arugment list. Details can be worked out. But first wanted to see if it makes preliminary sense to everyone? I'm looking specifically at language experts, what am I missing? In reference to the SI proposals (and keep in mind, this is still *orthogonal* to the format string vs. interpolation segments, which I'm still firmly on the side of passing the parsed data to the user), it could look something like this: ```d struct Interpolation { immutable string[] parts; } auto execi(Args...)(Database db, enum Interpolation interp, Args args) { enum sqlStr = generateSQLString(interp); return exec(db, sqlStr, args); } ``` The beauty of this is, the string interpolation proposal becomes a *direct translation* from the string literal to a sequence of simple values (similar to DIP1027). I know [one point from Jonathan/Nickolay](https://forum.dlang.org/post/lplyefclptgxonbzy zy forum.dlang.org) has already been brought up against this idea: On Sunday, 14 January 2024 at 07:54:23 UTC, Nickolay Bukreyev wrote:
 On Sunday, 14 January 2024 at 02:49:24 UTC, Jonathan M Davis 
 wrote:
 And I'd really rather not have runtime arguments silently 
 become compile-time arguments base on how a function signature 
 is written (which could change when the code is refactored).
Agreed. [story about removing unnecessary template instantiations by searching for !]
I see this point. I don't know how to reconcile my desire to have an expression that has compile-time data passed through it, while having it be an expression that works wherever expressions are expected, without having to separate the two into the compile time and runtime pieces. It's just so ugly, and things really should be where they belong cognitively. I'm open to alternatives. Or preliminary limitations. But that is the gist of it -- I want compile-time information passed via a string interpolation tuple, like it is for DIP1036e. -Steve
Jan 14
next sibling parent Nickolay Bukreyev <buknik95 ya.ru> writes:
You’ve created this thread a minute before I was going to post a 
reply. :)

I came up with a different solution.

```d
db.execi!i"SELECT $x, $(x + y)";
// Lowers to:
db.execi!(
     Interpolation(["SELECT ", "x", ", ", "x + y", ""]), () => x, 
() => x + y,
);
```

Pros:
* Does not complicate overload resolution.
* Does not magically translate runtime arguments to compile time.
* Does not require changes to the language (besides the parser, 
to support istrings).
* Can work with Steven’s `Interpolation`, DIP1036, or whatever 
else.
* Supports UFCS.
* Produces clean assembly (with LDC).

Cons:
* Does not support nested istrings.
* Requires a string mixin (but we can provide it in the library).

```d
import std.algorithm.iteration: map;
import std.array: join;
import std.conv: text;
import std.range: iota;

enum invokeAll(string name, size_t n) =
     iota(n).map!(i => text(name ~ '[', i, "](),")).join();

void execImpl(Interpolation interp, Args...)(Sqlite db, Args 
args) {
     import std.stdio;

     enum query = generateSql(interp);
     // Just an example.
     writeln(query);
     static foreach (i, arg; args)
         writeln('?', i + 1, " = ", arg);
}

void execi(Interpolation interp, args...)(Sqlite db) {
     mixin(`db.execImpl!interp(`, invokeAll!(`args`, args.length), 
`);`);
}
```

(`generateSql` can be obtained 
[here](https://forum.dlang.org/post/jgniissqzeybvmkdorlk forum.dlang.org),
section _Proposal 2_.)

Any feedback will be welcome.
Jan 14
prev sibling next sibling parent reply Nickolay Bukreyev <buknik95 ya.ru> writes:
Oops, I forgot to update my pros/cons. They are not comparing 
against the proposal described in this thread.

On Sunday, 14 January 2024 at 21:27:44 UTC, Steven Schveighoffer 
wrote:
 ```d
 void foo(T)(T x, enum string s)
 {
    pragma(msg, s); // s is compile-time
 }
 ```
The syntax looks nice (there’s nothing to remove, you know). You’ve already quoted my thoughts about the semantics. I think I have a challenge. Can you write a function that accepts an arbitrary number of key-value pairs? All keys should be compile-time strings; all values should be runtime ints; keys and values should alternate. ```d int value1, value2; foo("key1", value1, "key2", value2); ```
Jan 14
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On Sunday, 14 January 2024 at 22:02:21 UTC, Nickolay Bukreyev 
wrote:
 I think I have a challenge. Can you write a function that 
 accepts an arbitrary number of key-value pairs? All keys should 
 be compile-time strings; all values should be runtime ints; 
 keys and values should alternate.

 ```d
 int value1, value2;
 foo("key1", value1, "key2", value2);
 ```
Not with that calling syntax. You'd have to do it via a key array (compile time) and a value array (runtime, maybe variadic). Some of the SI proposals essentially do just this -- transform the argument order/structure. -Steve
Jan 14
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 1/14/24 22:27, Steven Schveighoffer wrote:
 On Friday, 12 January 2024 at 22:35:54 UTC, Walter Bright 
 [wrote](https://forum.dlang.org/post/unseru$167e$1 digitalmars.com):
 Given the interest in CTFE of istrings, I have been thinking about 
 making a general use case out of it instead of one specific to istrings.
Been also thinking about this and I have the following conclusions: - Walter's proposal seems super hacky and probably shouldn't be done. I don't like the idea of magically migrating an argument from one place to match a parameter in another, especially based on arbitrary rules that are constructed to fit the current use case (and in fact was shown not to actually help in the long run. - I really *really* like the idea of passing a simple struct at either runtime or compile time to a function over instrumented parameters (like the `InterpolationLiteral!"str"` or Rikki's attribute passing (which basically becomes extra hidden template parameters, that the function might not even use). - The syntax for any such mechanism should be obvious and straightforward. Instead of pushing runtime value arguments into compile time value parameters, why not just identify that they are compile time in the argument list? I like the [idea from Mathis Beer (FeepingCreature)](https://forum.dlang.org/post/wkwgipddfegevbrim av forum.dlang.org) that was linked by Nick earlier, but that also has some (I think) ambiguous syntax, and requires some coordination between separate parts. I'm going to propose something that furthers the baggage of `enum`, but that is the tool we currently have... Happy to think about better syntax. But why not just: ```d void foo(T)(T x, enum string s) {    pragma(msg, s); // s is compile-time } void main(string[] args) {    foo(1, "hi"); // ok    foo(1, "hello"); // ok, but a different instantiation    foo(1, args[1]); // error, second argument must be compile time } ``` Basically, an enum attributed parameter in the runtime parameter list requires a compile-time value, and implicitly adds it to the template arguments for the template instantiation. This is very similar to FeepingCreature's proposal, but without the extra ambiguity. There is no mechanism to explicitly pass the parameter in the compile-time arugment list. Details can be worked out. But first wanted to see if it makes preliminary sense to everyone? I'm looking specifically at language experts, what am I missing? In reference to the SI proposals (and keep in mind, this is still *orthogonal* to the format string vs. interpolation segments, which I'm still firmly on the side of passing the parsed data to the user), it could look something like this: ```d struct Interpolation {    immutable string[] parts; } auto execi(Args...)(Database db, enum Interpolation interp, Args args) {    enum sqlStr = generateSQLString(interp);    return exec(db, sqlStr, args); } ``` The beauty of this is, the string interpolation proposal becomes a *direct translation* from the string literal to a sequence of simple values (similar to DIP1027). I know [one point from Jonathan/Nickolay](https://forum.dlang.org/post/lplyefclptgxonbzy zy forum.dlang.org) has already been brought up against this idea: On Sunday, 14 January 2024 at 07:54:23 UTC, Nickolay Bukreyev wrote:
 On Sunday, 14 January 2024 at 02:49:24 UTC, Jonathan M Davis wrote:
 And I'd really rather not have runtime arguments silently become 
 compile-time arguments base on how a function signature is written 
 (which could change when the code is refactored).
Agreed. [story about removing unnecessary template instantiations by searching for !]
I see this point. I don't know how to reconcile my desire to have an expression that has compile-time data passed through it, while having it be an expression that works wherever expressions are expected, without having to separate the two into the compile time and runtime pieces. It's just so ugly, and things really should be where they belong cognitively. I'm open to alternatives. Or preliminary limitations. But that is the gist of it -- I want compile-time information passed via a string interpolation tuple, like it is for DIP1036e. -Steve
See also this: https://forum.dlang.org/post/unsgir$1a5m$1 digitalmars.com I think there is a way that is somewhat nicer than both of those ideas. 1. Allow `:` syntax for runtime parameters (this already works for template parameters): ```d void foo(int x:1, int y){ } // 1 void foo(int x:2, int y){ } // 2 void main(){ foo(1,2); // calls 1 foo(2,3); // calls 2 } ``` 2. Make it match during IFTI. ```d void foo(int x)(int:x, int y){ } void main(){ foo(1,2); // calls foo!1(2) foo(2,3); // calls foo!2(3) } ``` In this example, I have omitted the runtime parameter name, this is already a D feature. 3. Allow `enum` (and maybe `alias`) storage class on such parameters to omit them at runtime. ```d void foo(int x)(enum int:x, int y){ } void main(){ foo(1,2); // calls foo!1(2) foo(2,3); // calls foo!2(3) } ``` Maybe it is best to restrict `:` syntax to `enum` parameters but then at least the template parameter list is not being modified by the feature.
Jan 14
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 1/14/24 23:15, Timon Gehr wrote:
 ```d
 void foo(int x)(int:x, int y){ }
 
 void main(){
      foo(1,2); // calls foo!1(2)
      foo(2,3); // calls foo!2(3)
 }
 ```
As Steven points out, this should of course have been: ```d void foo(int x)(int:x, int y){ } void main(){ foo(1,2); // calls foo!1(1,2) foo(2,3); // calls foo!2(2,3) } ```
 
 Maybe it is best to restrict `:` syntax to `enum` parameters but then at least
the template parameter list is not being modified by the feature. 
But this still holds and then that example would not even compile.
Jan 14
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 1/14/24 23:15, Timon Gehr wrote:
 ```d
 void foo(int x)(enum int:x, int y){ }
 
 void main(){
      foo(1,2); // calls foo!1(2)
      foo(2,3); // calls foo!2(3)
 }
 ```
Also, this does not clarify my intention well. Here if you have: ```d alias foo1=foo!1; ``` and you call it like: ```d foo1(x,y) ``` Then `x` is evaluated at compile time and there is a check if it is indeed `1`.
Jan 14
prev sibling next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 14 January 2024 at 21:27:44 UTC, Steven Schveighoffer 
wrote:
 On Friday, 12 January 2024 at 22:35:54 UTC, Walter Bright 
 [wrote](https://forum.dlang.org/post/unseru$167e$1 digitalmars.com):
 Given the interest in CTFE of istrings, I have been thinking 
 about making a general use case out of it instead of one 
 specific to istrings.
Been also thinking about this and I have the following conclusions: [...]
Is any of this really necessary at all? D already has a way to pass data to a function at compile time (template parameters). It's not perfect, sure, and there are some use-cases where it's a bit awkward, but it gets the job done. Do we really need to add a whole new language feature just to have a second, slightly-different way of doing basically the same thing? In C++, this approach to language design has lead to having [seven different types of initialization][1], among other disasters. If we follow the same path with D, we will end up in the same place. [1]: https://en.cppreference.com/w/cpp/language/initialization
Jan 14
parent reply zjh <fqbqrr 163.com> writes:
On Sunday, 14 January 2024 at 22:49:31 UTC, Paul Backus wrote:

 In C++, this approach to language design has lead to having 
 [seven different types of initialization][1], among other 
 disasters. If we follow the same path with D, we will end up in 
 the same place.

 [1]: https://en.cppreference.com/w/cpp/language/initialization
In fact, the `initialization` of C++is better than that of D. ```d //C++; A a{1,2}; //VS,D: A a=A(1,2); ``` Users don't care about details!
Jan 14
parent reply bomat <Tempest_spam gmx.de> writes:
On Monday, 15 January 2024 at 03:43:04 UTC, zjh wrote:
 In fact, the `initialization` of C++is better than that of D.

 ```d
 //C++;
 A a{1,2};

 //VS,D:
 A a=A(1,2);
 ```
The problem with C++ is that the second syntax also works but does not do the same thing, which is just awful.
Jan 15
parent reply zjh <fqbqrr 163.com> writes:
On Monday, 15 January 2024 at 18:01:33 UTC, bomat wrote:

 The problem with C++ is that the second syntax also works but 
 does not do the same thing, which is just awful.
It's just a few `constructors`, it's easy to distinguish. And D even disabled the `default constructor`, I really don't understand why, it's completely `self binding`.
Jan 15
parent reply bomat <Tempest_spam gmx.de> writes:
On Tuesday, 16 January 2024 at 00:29:20 UTC, zjh wrote:
 It's just a few `constructors`, it's easy to distinguish.
It's a mess. Look, I don't want to derail this discussion, I cannot even judge the usefulness of the proposal, I just want to whole-heartedly agree with Paul Backus about the point that you can overdo it with adding bells and whistles, especially when you never deprecate (let alone remove) old stuff because of backwards compatibility. And if C++ is not a great example for that, I don't know what is. It's not "just a few constructors". At this point it's absolutely everything. Everything can be done in 7 different ways, but of course with tiny nuances that nobody can understand anymore. The logic by which a C++ compiler will generate default ctors and copy-operators based on which ones you have defined manually is literally quantum mechanics: look at the system too hard and you will change its behavior. Asylums are filled with people who tried to understand what actually happens when a C++ function returns a value. Whenever I meet people who call themselves "C++ experts" (e.g. in job interviews - great fun), I ask them for the difference between an lvalue, an rvalue, a glvalue, a prvalue, and an xvalue, just to show them how wrong they are. Usually they think I made those up, and I wish I had. The bottom line is: That level of complexity is "the last thing D needs": https://www.youtube.com/watch?v=KAWA1DuvCnQ
Jan 16
parent reply zjh <fqbqrr 163.com> writes:
On Tuesday, 16 January 2024 at 21:27:27 UTC, bomat wrote:

 Whenever I meet people who call themselves "C++ experts" (e.g. 
 in job interviews - great fun), I ask them for the difference 
 between an lvalue, an rvalue, a glvalue, a prvalue, and an 
 xvalue, just to show them how wrong they are. Usually they 
 think I made those up, and I wish I had.
 The bottom line is: That level of complexity is "the last thing 
 D needs":
 https://www.youtube.com/watch?v=KAWA1DuvCnQ
Left value, right value is very simple. Do you think `D language` does not have left value, right value?
Jan 16
parent reply zjh <fqbqrr 163.com> writes:
On Wednesday, 17 January 2024 at 00:36:52 UTC, zjh wrote:
Do you think `D
 language` does not have left value, right value?
As a user, I don't care about the complexity of `C++/D` at all. What I care about is that if it's not `easy` for me to use.
Jan 16
parent zjh <fqbqrr 163.com> writes:
On Wednesday, 17 January 2024 at 00:40:27 UTC, zjh wrote:

 What I care about is that if it's not easy for me to use.
And whether it meets my needs! Like `D`'s private ,is just part meet my need. so I am complaining it again and again!
Jan 16
prev sibling next sibling parent zjh <fqbqrr 163.com> writes:
On Sunday, 14 January 2024 at 21:27:44 UTC, Steven Schveighoffer 
wrote:

 - Walter's proposal seems super hacky and probably shouldn't be 
 done.
No, this is `very useful`. In the process of writing programs, many programs, sometimes requiring `compile time` and sometimes `runtime parameters`, they are mixed together. If they can `slide`, you can write only `one function` to meet various needs!
Jan 14
prev sibling next sibling parent zjh <fqbqrr 163.com> writes:
On Sunday, 14 January 2024 at 21:27:44 UTC, Steven Schveighoffer 
wrote:

 I'm going to propose something that furthers the baggage of 
 `enum`, but that is the tool we currently have... Happy to 
 think about better syntax. But why not just:

 ```d
 void foo(T)(T x, enum string s)
 {
    pragma(msg, s); // s is compile-time
 }
Formatting string is essentially a `compile time` parameter, placed within a runtime parameter and using a `sliding template` is a great idea. Sometimes, the parameters of some functions require both `compilation time and runtime`! I think it would be great to create a `sliding template` for uniformity.
Jan 14
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On Sunday, 14 January 2024 at 21:27:44 UTC, Steven Schveighoffer 
wrote:
 I'm going to propose something that furthers the baggage of 
 `enum`, but that is the tool we currently have... Happy to 
 think about better syntax. But why not just:

 ```d
 void foo(T)(T x, enum string s)
 {
    pragma(msg, s); // s is compile-time
 }

 void main(string[] args)
 {
    foo(1, "hi"); // ok
    foo(1, "hello"); // ok, but a different instantiation
    foo(1, args[1]); // error, second argument must be compile 
 time
 }
 ```
Unbeknownst to me, Quirin F. Schroll has already created a [pretty robust DIP](https://github.com/dlang/DIPs/pull/232) that is ready for proposing, describing basically exactly what I was proposing, with all the hairy details worked out. I apologize for not recognizing this, and hope that this DIP is considered and accepted! This indeed would enable a much simpler mechanism for the SI proposals to be dealt with at compile time. -Steve
Jan 15
parent Daniel N <no public.email> writes:
On Monday, 15 January 2024 at 16:49:24 UTC, Steven Schveighoffer 
wrote:
 Unbeknownst to me, Quirin F. Schroll has already created a 
 [pretty robust DIP](https://github.com/dlang/DIPs/pull/232) 
 that is ready for proposing, describing basically exactly what 
 I was proposing, with all the hairy details worked out.

 I apologize for not recognizing this, and hope that this DIP is 
 considered and accepted!

 This indeed would enable a much simpler mechanism for the SI 
 proposals to be dealt with at compile time.

 -Steve
I just want to add that C++20 went down a similar route... yet using auto. https://en.cppreference.com/w/cpp/language/function_template#Abbreviated_function_template Not as flexible as Quirins idea yet a little simpler. I don't mind either way... but could be good to know it exists before you decide on something.
Jan 15