Jump to page: 1 2
Thread overview
Yet another terrible compile time argument proposal
Jan 15
zjh
Jan 15
bomat
Jan 16
zjh
Jan 16
bomat
Jan 17
zjh
Jan 17
zjh
Jan 17
zjh
Jan 15
zjh
Jan 15
zjh
Jan 15
Daniel N
January 14

On Friday, 12 January 2024 at 22:35:54 UTC, Walter Bright wrote:

>

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) 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:

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:

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 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

January 14

You’ve created this thread a minute before I was going to post a reply. :)

I came up with a different solution.

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).
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, section Proposal 2.)

Any feedback will be welcome.

January 14

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:

>
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.

int value1, value2;
foo("key1", value1, "key2", value2);
January 14

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.

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

January 14
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/wkwgipddfegevbrimiav@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/lplyefclptgxonbzyzzy@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.
January 14
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.
January 14
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`.
January 14

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:

>

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, among other disasters. If we follow the same path with D, we will end up in the same place.

January 15

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!

January 15

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:

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.

« First   ‹ Prev
1 2