www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Discussion Thread: DIP 1036--String Interpolation Tuple

reply Mike Parker <aldacron gmail.com> writes:
This is the discussion thread for the second round of Community 
Review of DIP 1036, "String Interpolation Tuple Literals":

https://github.com/dlang/DIPs/blob/344e00ee2d6683d61ee019d5ef6c1a0646570093/DIPs/DIP1036.md

The review period will end at 11:59 PM ET on February 10, or when 
I make a post declaring it complete. Discussion in this thread 
may continue beyond that point.

Here in the discussion thread, you are free to discuss anything 
and everything related to the DIP. Express your support or 
opposition, debate alternatives, argue the merits, etc.

However, if you have any specific feedback on how to improve the 
proposal itself, then please post it in the feedback thread. The 
feedback thread will be the source for the review summary that I 
will write at the end of this review round. I will post a link to 
that thread immediately following this post. Just be sure to read 
and understand the Reviewer Guidelines before posting there:

https://github.com/dlang/DIPs/blob/master/docs/guidelines-reviewers.md

And my blog post on the difference between the Discussion and 
Feedback threads:

https://dlang.org/blog/2020/01/26/dip-reviews-discussion-vs-feedback/

Please stay on topic here. I will delete posts that are 
completely off-topic.
Jan 27 2021
next sibling parent Mike Parker <aldacron gmail.com> writes:
On Wednesday, 27 January 2021 at 10:33:08 UTC, Mike Parker wrote:

 However, if you have any specific feedback on how to improve 
 the proposal itself, then please post it in the feedback 
 thread. The feedback thread will be the source for the review 
 summary that I will write at the end of this review round. I 
 will post a link to that thread immediately following this post.
The Feedback Thread is here: https://forum.dlang.org/post/bvrejaayzpgbykacxrxe forum.dlang.org
Jan 27 2021
prev sibling next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Wednesday, 27 January 2021 at 10:33:08 UTC, Mike Parker wrote:
 This is the discussion thread for the second round of Community 
 Review of DIP 1036, "String Interpolation Tuple Literals":
IMO this version of the DIP has the same fundamental problem as the previous one: it's Good Work, but not Great Work. [1] I can imagine a version of DIP 1036 that *would* be Great Work--one that dropped the overweight .idup feature and shifted its focus to metaprogramming applications--but that's not the version the authors want to write, so I expect I'll never get to see it. [1] https://forum.dlang.org/post/q7u6g1$94p$1 digitalmars.com
Jan 27 2021
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Wednesday, 27 January 2021 at 14:36:10 UTC, Paul Backus wrote:
 shifted its focus to metaprogramming applications
What metaprogramming applications are you missing?
Jan 27 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Wednesday, 27 January 2021 at 14:52:39 UTC, Adam D. Ruppe 
wrote:
 On Wednesday, 27 January 2021 at 14:36:10 UTC, Paul Backus 
 wrote:
 shifted its focus to metaprogramming applications
What metaprogramming applications are you missing?
The problem isn't that anything's missing, it's that there's too much there in the first place. DIP 1036 is essentially two separate string interpolation proposals (the .idup version and the tuple version) awkwardly stitched together, and the presence of the stitching makes both of them worse than either would be on its own.
Jan 27 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/27/21 10:06 AM, Paul Backus wrote:
 On Wednesday, 27 January 2021 at 14:52:39 UTC, Adam D. Ruppe wrote:
 On Wednesday, 27 January 2021 at 14:36:10 UTC, Paul Backus wrote:
 shifted its focus to metaprogramming applications
What metaprogramming applications are you missing?
The problem isn't that anything's missing, it's that there's too much there in the first place. DIP 1036 is essentially two separate string interpolation proposals (the .idup version and the tuple version) awkwardly stitched together, and the presence of the stitching makes both of them worse than either would be on its own.
I have the exact opposite experience. The previous DIP elicited multiple questions on why it was so complex to just get a string out of such a literal. Why do I have to add `.text` or `.idup` they would say. This includes Andrei, who said he would never use the tuple form, just the string form, and if it didn't "just work" it was a failed feature (this is my recollection from a phone call, so it's possible I misunderstood). Without the tuple portion, it's just another run-of-the-mill string interpolation feature, which is OK, but just leaves all of D's power on the table. Without the auto-conversion to string, it's a never-been-seen-before metaprogramming feature that people just looking to get a string from their data will have a hard time using. With both together, it's a cohesive (not awkward, fully disagree on that) setup that does the intuitive thing if you don't know how it actually works under the hood, but provide the exact power you need for hooking string interpolation when you know about it, and ONLY if you want it. I can't imagine how you could make this more metaprogramming friendly than it is. I find it to be Great Work as defined by your link. In fact, part of the inspiration of adding the idup rewrite was from the talk you referred to (in the first review) from Scott Meyers where he says that the most obvious thing should be what it does. The most obvious thing you get from a string literal is a string. You and others may disagree, but if this doesn't make it through, I don't know what would. -Steve
Jan 27 2021
parent reply SealabJaster <sealabjaster gmail.com> writes:
On Wednesday, 27 January 2021 at 16:54:30 UTC, Steven 
Schveighoffer wrote:
 ...
+1 handles things, and not having to jump through hoops just to make a string is a lot more obvious and user-friendly. I fear the ultimate fate of this DIP is rejection though, but in my not so honest opinion this is a better iteration of the previous attempt. Honestly, pretty much all of my major points have already been argued by others during the last discussion of this DIP, so I don't really have anything new to add other than: What's the main priority between making it easy to use and obvious, or making it less easy to use for... reasons? Why are we scared of adding usage of the GC for small quality of life things (implicit string conversion)? Even if the DIP specifies a way to avoid GC usage? It's kind of like we're silently admitting that the GC is a hindrance, and so is to be avoided for any new features, which in making the obvious, common use cases (implicit string conversion) harder to use. Which is why I believe this DIP's surface value is much better than the attempt before. I'm not able to comment on all the nitty gritty technical details though, which I understand may have issues. Again though, I'm largely biased towards the most common use case being the simplest and most obvious use case. Which for people implicit string conversion that's out of your way. And disclaimer on these thoughts: I personally have no interest in the tuple form of an interp string in spite of recognising its value and potential, so my bias is simply towards string conversion. Finally, and I'm sorry if I missed this being described in the DIP, but consider this case (and similar): ``` void f(T)(T array) if(isArray!T) { //... } f(i"..."); ``` What would happen here? My best guess is that it won't compile unless written as `f(i"...".idup)` which then introduces an odd discrepancy where there's certain usages that still require explicitly calling .idup, e.g. `i"...".idup.splitter`
Jan 28 2021
parent Walter Bright <newshound2 digitalmars.com> writes:
On 1/28/2021 12:46 AM, SealabJaster wrote:
 Finally, and I'm sorry if I missed this being described in the DIP, but
consider 
 this case (and similar):
 
 ```
 void f(T)(T array)
 if(isArray!T)
 {
      //...
 }
 
 f(i"...");
 ```
 
 What would happen here? My best guess is that it won't compile unless written
as 
 `f(i"...".idup)` which then introduces an odd discrepancy where there's
certain 
 usages that still require explicitly calling .idup, e.g. `i"...".idup.splitter`
This does appear to be an omission, please file it in the feedback thread.
Jan 29 2021
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
It appears to have abandoned being usable with printf?
Jan 27 2021
next sibling parent reply Dukc <ajieskola gmail.com> writes:
On Thursday, 28 January 2021 at 06:12:14 UTC, Walter Bright wrote:
 It appears to have abandoned being usable with printf?
Well a simple way is `printf("%s", text(i"hello ${name1}, ${name2} and {name3}!"))`. Works, but not in a no-gc way unless you redefine `Interp!()` yourself. Even without touching `Interp!()` you could do a function that prints an interpolated string with `printf` without using the GC, as long as the format specifier is known at compile time. The function introspects the received `Interp!string` struct types and makes the correct format specifier from them at compile-time and feeds it along with rest of the arguments to `printf`. This is more complicated of course.
Jan 27 2021
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/27/2021 11:16 PM, Dukc wrote:
 On Thursday, 28 January 2021 at 06:12:14 UTC, Walter Bright wrote:
 It appears to have abandoned being usable with printf?
Well a simple way is `printf("%s", text(i"hello ${name1}, ${name2} and {name3}!"))`. Works, but not in a no-gc way unless you redefine `Interp!()` yourself.
Nobody is going to prefer calling printf that way. Redefining a custom Interp template is a no-go, too, as then the code is incompatible with common usage code.
 Even without touching `Interp!()` you could do a function that prints an 
 interpolated string with `printf` without using the GC, as long as the format 
 specifier is known at compile time. The function introspects the received 
 `Interp!string` struct types and makes the correct format specifier from them
at 
 compile-time and feeds it along with rest of the arguments to `printf`. This
is 
 more complicated of course.
Sounds complicated enough to be a non-starter.
Jan 27 2021
next sibling parent Dukc <ajieskola gmail.com> writes:
On Thursday, 28 January 2021 at 07:44:05 UTC, Walter Bright wrote:
 On 1/27/2021 11:16 PM, Dukc wrote:
 On Thursday, 28 January 2021 at 06:12:14 UTC, Walter Bright 
 wrote:
 It appears to have abandoned being usable with printf?
Well a simple way is `printf("%s", text(i"hello ${name1}, ${name2} and {name3}!"))`. Works, but not in a no-gc way unless you redefine `Interp!()` yourself.
Nobody is going to prefer calling printf that way.
Yeah, easier to just use `std.stdio.write`.
 Redefining a custom Interp template is a no-go, too, as then 
 the code is incompatible with common usage code.
I wouldn't like that either.
 Even without touching `Interp!()` you could do a function that 
 prints an interpolated string with `printf` without using the 
 GC, as long as the format specifier is known at compile time. 
 The function introspects the received `Interp!string` struct 
 types and makes the correct format specifier from them at 
 compile-time and feeds it along with rest of the arguments to 
 `printf`. This is more complicated of course.
Sounds complicated enough to be a non-starter.
...but on that I disagree. It can be a library function. I am not sure whether it should be in Phobos, in some DUB package, or just copy-pasted around, but in any case one would not need to face the complexity oneself to use it.
Jan 28 2021
prev sibling parent reply SealabJaster <sealabjaster gmail.com> writes:
On Thursday, 28 January 2021 at 07:44:05 UTC, Walter Bright wrote:
 Nobody is going to prefer calling printf that way.
Forgive me if I'm missing the point, but why should a D feature compromise itself for a small subset of C functions? Kind of ironically the argument of "nobody is going to prefer calling printf that way" is similar to the argument of the previous printf-compatible proposal having to call an external function just to get a string.
Jan 28 2021
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/28/2021 12:50 AM, SealabJaster wrote:
 On Thursday, 28 January 2021 at 07:44:05 UTC, Walter Bright wrote:
 Nobody is going to prefer calling printf that way.
Forgive me if I'm missing the point, but why should a D feature compromise itself for a small subset of C functions?
While the printf family is indeed a small subset of C functions, it is probably the most called one.
Jan 28 2021
parent reply 12345swordy <alexanderheistermann gmail.com> writes:
On Thursday, 28 January 2021 at 20:02:40 UTC, Walter Bright wrote:
 On 1/28/2021 12:50 AM, SealabJaster wrote:
 On Thursday, 28 January 2021 at 07:44:05 UTC, Walter Bright 
 wrote:
 Nobody is going to prefer calling printf that way.
Forgive me if I'm missing the point, but why should a D feature compromise itself for a small subset of C functions?
While the printf family is indeed a small subset of C functions, it is probably the most called one.
That being said. Would it be easier to convince you to accept this if there is a prototype version of this? - Alex
Jan 28 2021
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/28/2021 12:34 PM, 12345swordy wrote:
 That being said. Would it be easier to convince you to accept this if there is
a 
 prototype version of this?
Prototype of a way to call printf, or prototype of the DIP?
Jan 28 2021
parent reply 12345swordy <alexanderheistermann gmail.com> writes:
On Friday, 29 January 2021 at 04:21:24 UTC, Walter Bright wrote:
 On 1/28/2021 12:34 PM, 12345swordy wrote:
 That being said. Would it be easier to convince you to accept 
 this if there is a prototype version of this?
Prototype of a way to call printf, or prototype of the DIP?
Prototype of the dip. -Alex
Jan 29 2021
parent Walter Bright <newshound2 digitalmars.com> writes:
On 1/29/2021 7:22 AM, 12345swordy wrote:
 On Friday, 29 January 2021 at 04:21:24 UTC, Walter Bright wrote:
 On 1/28/2021 12:34 PM, 12345swordy wrote:
 That being said. Would it be easier to convince you to accept this if there 
 is a prototype version of this?
Prototype of a way to call printf, or prototype of the DIP?
Prototype of the dip.
No, because it's not a question of can it work (presuming that the DIP was improved to more accurately specify it).
Jan 30 2021
prev sibling next sibling parent Ogi <ogion.art gmail.com> writes:
On Thursday, 28 January 2021 at 06:12:14 UTC, Walter Bright wrote:
 It appears to have abandoned being usable with printf?
Ranges don’t work with C functions, but that’s hardly an argument against ranges.
Jan 28 2021
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/28/21 1:12 AM, Walter Bright wrote:
 It appears to have abandoned being usable with printf?
Yes and no. Yes, we do not think that using printf with interpolated strings *without modification* is an important goal. printf works fine as it is. This is D, not C. writeln IS usable without modifications. And no, it is not abandoned, because it's perfectly possible because of the awesome power of metaprogramming allowed in this DIP: https://run.dlang.io/is/X5HcXS -Steve
Jan 28 2021
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/28/2021 6:42 AM, Steven Schveighoffer wrote:
 On 1/28/21 1:12 AM, Walter Bright wrote:
 It appears to have abandoned being usable with printf?
Yes and no. Yes, we do not think that using printf with interpolated strings *without modification* is an important goal. printf works fine as it is. This is D, not C. writeln IS usable without modifications. And no, it is not abandoned, because it's perfectly possible because of the awesome power of metaprogramming allowed in this DIP: https://run.dlang.io/is/X5HcXS
Interesting, but it's really writing your own printf, and one that doesn't accept any format modifiers.
Jan 28 2021
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 29 January 2021 at 04:25:54 UTC, Walter Bright wrote:
 Interesting, but it's really writing your own printf, and one 
 that doesn't accept any format modifiers.
It is trivial to accept format modifiers here too, his example just generated them from type info because you can. It could just as well be printf(i"%s${item} %02d${other_item}"); which expands to the inlinable call printf("%s %02d", item, other_item); by means of simple metaprogramming (you concat the strings at compile time - no runtime cost, it reduces to an eponymous template with an enum string - then build the call out of the remaining filtered tuple). You can even do a CTFE scan of the string if you like and see that there's a % already and use it, or there isn't one and insert one. While this is an overload to do the CTFE manipulations, they are indeed CTFE manipulations and thus have no runtime footprint; it reduces to an inlined call to the original thing. Same assembly. D excels at these things.
Jan 28 2021
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/28/2021 8:47 PM, Adam D. Ruppe wrote:
 D excels at these things.
Ok, it's doable, but it's writing your own printf to do these manipulations.
Jan 29 2021
next sibling parent reply rikki cattermole <rikki cattermole.co.nz> writes:
On 29/01/2021 9:02 PM, Walter Bright wrote:
 On 1/28/2021 8:47 PM, Adam D. Ruppe wrote:
 D excels at these things.
Ok, it's doable, but it's writing your own printf to do these manipulations.
I agree, we shouldn't need to write the mapper function which Steven wrote ourselves. We (Steven and I) discussed this on Discord and left it at the function should not be included in the DIP. From a UX POV, it should be just a matter of doing an alias IMO. But that isn't really a goal of a DIP to specify such utility.
Jan 29 2021
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/29/2021 12:46 AM, rikki cattermole wrote:
 I agree, we shouldn't need to write the mapper function which Steven wrote 
 ourselves.
 
 We (Steven and I) discussed this on Discord and left it at the function should 
 not be included in the DIP. From a UX POV, it should be just a matter of doing 
 an alias IMO. But that isn't really a goal of a DIP to specify such utility.
Even if it becomes part of Phobos, overloading C functions imported from external C libraries in general makes me uncomfortable, because it hides the fact that the C function isn't being called. I never liked C macros like: #define printf(...) printf("abc" ...) for the same reason.
Jan 29 2021
next sibling parent reply Rikki Cattermole <alphaglosined gmail.com> writes:
On Friday, 29 January 2021 at 08:57:13 UTC, Walter Bright wrote:
 On 1/29/2021 12:46 AM, rikki cattermole wrote:
 I agree, we shouldn't need to write the mapper function which 
 Steven wrote ourselves.
 
 We (Steven and I) discussed this on Discord and left it at the 
 function should not be included in the DIP. From a UX POV, it 
 should be just a matter of doing an alias IMO. But that isn't 
 really a goal of a DIP to specify such utility.
Even if it becomes part of Phobos, overloading C functions imported from external C libraries in general makes me uncomfortable, because it hides the fact that the C function isn't being called. I never liked C macros like: #define printf(...) printf("abc" ...) for the same reason.
Would a pragma i.e. pragma(rewriteStringIntoFormatVarargs) extern(C) int printf(const char* fmt, ...); Make you feel more comfortable with this DIP? This way only functions which the binding creator says fall into this style can be called via a interpolated string correctly. Without any overloads or anything else.
Jan 29 2021
parent Walter Bright <newshound2 digitalmars.com> writes:
On 1/29/2021 1:15 AM, Rikki Cattermole wrote:
 Would a pragma i.e.
 
 pragma(rewriteStringIntoFormatVarargs)
 extern(C) int printf(const char* fmt, ...);
 
 Make you feel more comfortable with this DIP?
 
 This way only functions which the binding creator says fall into this style
can 
 be called via a interpolated string correctly. Without any overloads or
anything 
 else.
The more special cases there are, the more unwieldy the language becomes, and the more unexpected interactions and unforeseen bugs.
Jan 29 2021
prev sibling parent Dukc <ajieskola gmail.com> writes:
On Friday, 29 January 2021 at 08:57:13 UTC, Walter Bright wrote:
 Even if it becomes part of Phobos, overloading C functions 
 imported from external C libraries in general makes me 
 uncomfortable, because it hides the fact that the C function 
 isn't being called. I never liked C macros like:

     #define printf(...) printf("abc" ...)

 for the same reason.
In that case we can just pick a different for the mapper function. `printI` for example.
Jan 29 2021
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 3:02 AM, Walter Bright wrote:
 On 1/28/2021 8:47 PM, Adam D. Ruppe wrote:
 D excels at these things.
Ok, it's doable, but it's writing your own printf to do these manipulations.
It's doing what you want because it's possible. When someone says "yeah but I can't do this", and I say "OK, here's a wrapper I wrote in 15 minutes that does what you want" and you say "But that means I need a wrapper!", I don't really know where to go from here. You asked for something that works with printf, I gave it to you. If you don't like it, I don't know what to say, but it's trivially easy and can be inlined to a direct call. What if I complained that safe feature is unusable because I had to wrap the OS `read` as a trusted function? Would that be a reasonable argument? It requires a simple wrapper, write it and be done. Or don't use safe code. In fact DIP1027 CANNOT make an overload for printf that's usable in D code, and the existing printf can't be used with strings (ironically the default specifier for DIP1027) without horrible syntax. If you want to use printf without a wrapper, use printf as it was intended. If you want to use printf with interpolation strings, use a trivial wrapper. -Steve
Jan 29 2021
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/29/2021 9:18 AM, Steven Schveighoffer wrote:
 On 1/29/21 3:02 AM, Walter Bright wrote:
 On 1/28/2021 8:47 PM, Adam D. Ruppe wrote:
 D excels at these things.
Ok, it's doable, but it's writing your own printf to do these manipulations.
It's doing what you want because it's possible. When someone says "yeah but I can't do this", and I say "OK, here's a wrapper I wrote in 15 minutes that does what you want" and you say "But that means I need a wrapper!", I don't really know where to go from here. You asked for something that works with printf, I gave it to you. If you don't like it, I don't know what to say, but it's trivially easy and can be inlined to a direct call. What if I complained that safe feature is unusable because I had to wrap the OS `read` as a trusted function? Would that be a reasonable argument? It requires a simple wrapper, write it and be done. Or don't use safe code. In fact DIP1027 CANNOT make an overload for printf that's usable in D code, and the existing printf can't be used with strings (ironically the default specifier for DIP1027) without horrible syntax. If you want to use printf without a wrapper, use printf as it was intended. If you want to use printf with interpolation strings, use a trivial wrapper.
It's simply that if I want to use it with printf, I have to write my own printf intermediary. That's not using it with printf. It's kinda trivially obvious that someone can write their own functions to do things, but pretending it is printf by naming it printf just puts a layer of confusion in for the reader. printf is so ubiquitous that it brings expectations when saying printf can be called that printf is actually being called, rather than some intermediary one must write oneself. Personally, I'd reject any PRs that contained an overload for printf or any other member of the C Standard Library. The same applies to the mysql example in the DIP - the user has to write their own intermediary to get it to work. Also, needing the GC for this to work with printf is a problem, as one cannot use it with betterC. Having this GC call hidden is not good, as D programmers calling printf are likely doing it because they want the low overhead and predictable behavior that printf provides. (I'm actually not sure if the GC would be required for the printf intermediary, which is another problem, as it isn't obvious when it is needed or not.)
Jan 30 2021
next sibling parent reply claptrap <clap trap.com> writes:
On Sunday, 31 January 2021 at 05:37:09 UTC, Walter Bright wrote:
 On 1/29/2021 9:18 AM, Steven Schveighoffer wrote:

 It's simply that if I want to use it with printf, I have to 
 write my own printf intermediary. That's not using it with 
 printf. It's kinda trivially obvious that someone can write 
 their own functions to do things, but pretending it is printf 
 by naming it printf just puts a layer of confusion in for the 
 reader. printf is so ubiquitous that it brings expectations 
 when saying printf can be called that printf is actually being 
 called, rather than some intermediary one must write oneself. 
 Personally, I'd reject any PRs that contained an overload for 
 printf or any other member of the C Standard Library.
Why is "working with printf" so important? I mean all the current code that uses printf can just carry on using it. Anyone who wants to use string interpolation can just start using iprintf from the d stdlib or whatever. I mean i dont see why string interpolation working with printf is so important that it should put such constraints on the more modern aspects of D. It's a legacy C function, let it be that.
Jan 31 2021
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/31/2021 3:04 AM, claptrap wrote:
 Why is "working with printf" so important?
It's a canary. printf has a very simple interface, and if string interpolation doesn't work for simple cases, it is indicative of complexity problems. Along with, of course, everybody uses printf and printf-style functions (dmd has many), and writef also uses a printf-style format. I emphasize that #DIP1027 knows nothing about printf or its formats, other than having the default format be `%s`. The default can, of course, be overridden. Again, DIP1027 KNOWS NOTHING ABOUT PRINTF OR ITS FORMATS.
Feb 01 2021
next sibling parent reply Max Haughton <maxhaton gmail.com> writes:
On Tuesday, 2 February 2021 at 06:22:11 UTC, Walter Bright wrote:
 On 1/31/2021 3:04 AM, claptrap wrote:
 Why is "working with printf" so important?
It's a canary. printf has a very simple interface, and if string interpolation doesn't work for simple cases, it is indicative of complexity problems. Along with, of course, everybody uses printf and printf-style functions (dmd has many), and writef also uses a printf-style format. I emphasize that #DIP1027 knows nothing about printf or its formats, other than having the default format be `%s`. The default can, of course, be overridden. Again, DIP1027 KNOWS NOTHING ABOUT PRINTF OR ITS FORMATS.
Cheap solution: p"Blah blah {blah}" for printf style, i"xyz" for everything else?
Feb 02 2021
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Tuesday, 2 February 2021 at 08:16:11 UTC, Max Haughton wrote:
 Cheap solution: p"Blah blah {blah}" for printf style, i"xyz" 
 for everything else?
Or just a (zero runtime cost!) library function which is possible with the dip as-is. D excels at these things and there's no need to special case the compiler for it. No more literal tokens will be added to this DIP. No qq"", no p"". It is all off the table. The only major question I'm willing to entertain at this point is if the implicit string conversion is worth the cost. Everything else needs to be focused on getting the other little details right (like whatever it does with mixin) BTW I have a 80% implementation up already (no implicit string impl and only i"" syntax added) so you can play with it yourself: https://github.com/dlang/dmd/compare/master...adamdruppe:string_interp The druntime components are struct interp(string s) { string toString() const { return s; } } And repeat for wstring and dstring if you like. Those must be in object.d for it to work. For the demo, just use phobos' std.conv.text to convert it to a plain string (string s = i"anything".text;) or start writing your own functions to really explore the possibilities.
Feb 02 2021
parent reply jmh530 <john.michael.hall gmail.com> writes:
On Tuesday, 2 February 2021 at 13:28:52 UTC, Adam D. Ruppe wrote:
 On Tuesday, 2 February 2021 at 08:16:11 UTC, Max Haughton wrote:
 Cheap solution: p"Blah blah {blah}" for printf style, i"xyz" 
 for everything else?
Or just a (zero runtime cost!) library function which is possible with the dip as-is. D excels at these things and there's no need to special case the compiler for it. [snip]
I feel like you or Steven provided an example of this before, but I can't recall where. Is it worth it to provide a simple example of it in the DIP?
Feb 02 2021
parent jmh530 <john.michael.hall gmail.com> writes:
On Tuesday, 2 February 2021 at 14:31:53 UTC, jmh530 wrote:
 On Tuesday, 2 February 2021 at 13:28:52 UTC, Adam D. Ruppe 
 wrote:
 On Tuesday, 2 February 2021 at 08:16:11 UTC, Max Haughton 
 wrote:
 Cheap solution: p"Blah blah {blah}" for printf style, i"xyz" 
 for everything else?
Or just a (zero runtime cost!) library function which is possible with the dip as-is. D excels at these things and there's no need to special case the compiler for it. [snip]
I feel like you or Steven provided an example of this before, but I can't recall where. Is it worth it to provide a simple example of it in the DIP?
Ah, here it is https://forum.dlang.org/post/ruuigk$1bp8$1 digitalmars.com
Feb 02 2021
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/2/21 1:22 AM, Walter Bright wrote:
 On 1/31/2021 3:04 AM, claptrap wrote:
 Why is "working with printf" so important?
It's a canary. printf has a very simple interface, and if string interpolation doesn't work for simple cases, it is indicative of complexity problems.
To me this sounds like the wrong litmus test. printf: separate format from data interpolation: mix format with data It doesn't seem to me at all that the first thing I'd want to do with interpolated strings is to contort them into a use of printf.
 Along with, of course, everybody uses printf and printf-style functions 
 (dmd has many), and writef also uses a printf-style format.
 
 I emphasize that #DIP1027 knows nothing about printf or its formats, 
 other than having the default format be `%s`. The default can, of 
 course, be overridden.
 
 Again, DIP1027 KNOWS NOTHING ABOUT PRINTF OR ITS FORMATS.
This is good. We'd do good to keep printf out of the interpolation discussion. CODE GENERATION IS WHERE IT'S AT.
Feb 02 2021
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 2/2/2021 6:20 AM, Andrei Alexandrescu wrote:
 CODE GENERATION IS WHERE IT'S AT.
How is that different?
Feb 02 2021
parent Max Haughton <maxhaton gmail.com> writes:
On Tuesday, 2 February 2021 at 23:06:00 UTC, Walter Bright wrote:
 On 2/2/2021 6:20 AM, Andrei Alexandrescu wrote:
 CODE GENERATION IS WHERE IT'S AT.
How is that different?
I think the point here is that it's not worth killing the golden goose just to have printf work easily - I personally think a solution is obviously possible even if not through this DIP. Ergonomically doing code generation with format specifiers is just really really ugly and brittle. I feel similarly with printf although I still use it as writeln compiles to a remarkable amount of machine code. One thing that's just struck me is that with move semantics this could have a bunch of potential GC usage eliminated for relatively little cost by using some structs in the right place.
Feb 02 2021
prev sibling parent reply claptrap <clap trap.com> writes:
On Tuesday, 2 February 2021 at 06:22:11 UTC, Walter Bright wrote:
 On 1/31/2021 3:04 AM, claptrap wrote:
 Why is "working with printf" so important?
It's a canary. printf has a very simple interface, and if string interpolation doesn't work for simple cases, it is indicative of complexity problems.
Making the DIP work with printf wont make it work with all simple interfaces. So printf is not "a canary for simple interfaces". All it actually does is make it work with a very specific subset of "simple interfaces" at the expense of all others. You solved the equations for one variable, but you still have 3 unknowns.
 Along with, of course, everybody uses printf and printf-style 
 functions (dmd has many), and writef also uses a printf-style 
 format.
I dont, I have a few custom wrapper functions for formatting floats, and i use writeln.
 I emphasize that #DIP1027 knows nothing about printf or its 
 formats, other than having the default format be `%s`. The 
 default can, of course, be overridden.

 Again, DIP1027 KNOWS NOTHING ABOUT PRINTF OR ITS FORMATS.
I dont like either proposal tbh.
Feb 02 2021
parent reply Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Tuesday, 2 February 2021 at 14:45:05 UTC, claptrap wrote:
 On Tuesday, 2 February 2021 at 06:22:11 UTC, Walter Bright 
 wrote:
 On 1/31/2021 3:04 AM, claptrap wrote:
 Why is "working with printf" so important?
It's a canary. printf has a very simple interface, and if string interpolation doesn't work for simple cases, it is indicative of complexity problems.
Making the DIP work with printf wont make it work with all simple interfaces. So printf is not "a canary for simple interfaces". All it actually does is make it work with a very specific subset of "simple interfaces" at the expense of all others. You solved the equations for one variable, but you still have 3 unknowns.
 Along with, of course, everybody uses printf and printf-style 
 functions (dmd has many), and writef also uses a printf-style 
 format.
I dont, I have a few custom wrapper functions for formatting floats, and i use writeln.
 I emphasize that #DIP1027 knows nothing about printf or its 
 formats, other than having the default format be `%s`. The 
 default can, of course, be overridden.

 Again, DIP1027 KNOWS NOTHING ABOUT PRINTF OR ITS FORMATS.
I dont like either proposal tbh.
Anyway you look at this it seems there's no clear concensus on how it should work. I really want this to pass, but not if there are many corner cases and/or special rules to follow. It should be as simple as possible while also being as powerful as possible. It's a tough balance.
Feb 02 2021
parent reply 12345swordy <alexanderheistermann gmail.com> writes:
On Tuesday, 2 February 2021 at 18:41:37 UTC, Imperatorn wrote:
 On Tuesday, 2 February 2021 at 14:45:05 UTC, claptrap wrote:
 [...]
Anyway you look at this it seems there's no clear concensus on how it should work. I really want this to pass, but not if there are many corner cases and/or special rules to follow. It should be as simple as possible while also being as powerful as possible. It's a tough balance.
You don't need a clear consensus on this in order to pass, as you just need to convince two people here. -Alex
Feb 02 2021
parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Tuesday, 2 February 2021 at 19:38:08 UTC, 12345swordy wrote:
 On Tuesday, 2 February 2021 at 18:41:37 UTC, Imperatorn wrote:
 On Tuesday, 2 February 2021 at 14:45:05 UTC, claptrap wrote:
 [...]
Anyway you look at this it seems there's no clear concensus on how it should work. I really want this to pass, but not if there are many corner cases and/or special rules to follow. It should be as simple as possible while also being as powerful as possible. It's a tough balance.
You don't need a clear consensus on this in order to pass, as you just need to convince two people here. -Alex
True, but I guess they read the comments.
Feb 02 2021
prev sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Sunday, 31 January 2021 at 05:37:09 UTC, Walter Bright wrote:
 Also, needing the GC for this to work with printf is a problem, 
 as one cannot use it with betterC.
This DIP does not require the GC to work with printf. The little forwarding function Steven demonstrated is 100% compatible with betterC.
Jan 31 2021
parent Walter Bright <newshound2 digitalmars.com> writes:
On 1/31/2021 3:28 AM, Adam D. Ruppe wrote:
 On Sunday, 31 January 2021 at 05:37:09 UTC, Walter Bright wrote:
 Also, needing the GC for this to work with printf is a problem, as one cannot 
 use it with betterC.
This DIP does not require the GC to work with printf. The little forwarding function Steven demonstrated is 100% compatible with betterC.
Thank you for clarifying.
Jan 31 2021
prev sibling next sibling parent FeepingCreature <feepingcreature gmail.com> writes:
On Thursday, 28 January 2021 at 06:12:14 UTC, Walter Bright wrote:
 It appears to have abandoned being usable with printf?
I suspect the sorts of people who use printf would probably continue using explicit format strings. There is an axis of features from, for lack of better terms, "austere" to "luxurious". On that axis, printf is on one side and writefln/format are on the other. Format strings are a "luxurious" feature - like templates and DSLs, they give up fine-grained control over the actual function calls that take place, and in return gain expressivity and convenience. It's not like format strings break printf; printf remains callable as always. But if you are the sort of person who used to use a templated print function like formattedWrite, with its deep forest of generated code where you don't really care how it goes from input to output so much as that it just adequately prints the data you gave it with as little fuss as achievable - in other words, the sort of person who would welcome and prefer inline format strings - then you usually don't use printf to start with, because printf is a function that is *so* unintuitive and lowlevel that it needs its own *class* of linters. You use printf when you want to precisely guide how your function arguments are parsed, down to the level of stack size. I don't think that is the usecase of format strings, and satisfying it will probably hurt the proposal.
Jan 29 2021
prev sibling parent Jesse Phillips <Jesse.K.Phillips+D gmail.com> writes:
On Thursday, 28 January 2021 at 06:12:14 UTC, Walter Bright wrote:
 It appears to have abandoned being usable with printf?
After reading through the discussion, would it be more prudent to request that it can be used with printf and nogc, even if an additional template is required to translate? D has created a focus on working well without a GC. So it would make sense that new features would be aligned with that concept. Whereas old language features can both remain using GC and not supporting printf. ``` import std; import core.stdc.stdio; nogc void main() {    auto W = "world" ;    printf(toStringz("Hello " ~ W ~ " D")); } ```
Feb 04 2021
prev sibling next sibling parent reply 12345swordy <alexanderheistermann gmail.com> writes:
On Wednesday, 27 January 2021 at 10:33:08 UTC, Mike Parker wrote:
 This is the discussion thread for the second round of Community 
 Review of DIP 1036, "String Interpolation Tuple Literals":

 https://github.com/dlang/DIPs/blob/344e00ee2d6683d61ee019d5ef6c1a0646570093/DIPs/DIP1036.md

 The review period will end at 11:59 PM ET on February 10, or 
 when I make a post declaring it complete. Discussion in this 
 thread may continue beyond that point.

 Here in the discussion thread, you are free to discuss anything 
 and everything related to the DIP. Express your support or 
 opposition, debate alternatives, argue the merits, etc.

 However, if you have any specific feedback on how to improve 
 the proposal itself, then please post it in the feedback 
 thread. The feedback thread will be the source for the review 
 summary that I will write at the end of this review round. I 
 will post a link to that thread immediately following this 
 post. Just be sure to read and understand the Reviewer 
 Guidelines before posting there:

 https://github.com/dlang/DIPs/blob/master/docs/guidelines-reviewers.md

 And my blog post on the difference between the Discussion and 
 Feedback threads:

 https://dlang.org/blog/2020/01/26/dip-reviews-discussion-vs-feedback/

 Please stay on topic here. I will delete posts that are 
 completely off-topic.
I am going to respond a comment in the feed back thread that bugs me.
 requires the GC
D needs to move away from such constructs.
No, it doesn't. I am starting to believe that the gc phobia that has been spread by the c++/c people has been a mistake, if d is starting to turn down productivity features because of it. -Alex
Jan 28 2021
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Jan 28, 2021 at 02:03:42PM +0000, 12345swordy via Digitalmars-d wrote:
[...]
 requires the GC
 D needs to move away from such constructs.
No, it doesn't. I am starting to believe that the gc phobia that has been spread by the c++/c people has been a mistake, if d is starting to turn down productivity features because of it.
[...] +1. Though at the rate this ill-fated DIP has been going, I fear it may already be DOA. :-/ T -- He who does not appreciate the beauty of language is not worthy to bemoan its flaws.
Jan 28 2021
prev sibling parent reply bachmeier <no spam.net> writes:
On Thursday, 28 January 2021 at 14:03:42 UTC, 12345swordy wrote:

 requires the GC
D needs to move away from such constructs.
No, it doesn't. I am starting to believe that the gc phobia that has been spread by the c++/c people has been a mistake, if d is starting to turn down productivity features because of it.
For those of us that want to do productive things with our time rather than fiddle around in the weeds of memory management, this is not a good sign. If I wanted to use Rust, I'd already be using it.
Jan 28 2021
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/28/2021 7:19 AM, bachmeier wrote:
 For those of us that want to do productive things with our time rather than 
 fiddle around in the weeds of memory management, this is not a good sign. If I 
 wanted to use Rust, I'd already be using it.
Sure, but #DIP1027 did not require a GC or fiddling around. So it's not like it's not possible.
Jan 29 2021
parent reply Dukc <ajieskola gmail.com> writes:
On Friday, 29 January 2021 at 11:09:57 UTC, Walter Bright wrote:
 Sure, but #DIP1027 did not require a GC or fiddling around. So 
 it's not like it's not possible.
You need to remember that perhaps the most important use case for string interpolation are string mixins. With DIP1027, one would have to write `mixin(i"foreach(i;0..${iterations}) foo()".format)` instead of `mixin(i"foreach(i;0..${iterations}) foo()")` that works with this proposal. Added `.format` for every single interpolated string mixin. Let's face it, having to import a library function to use `printf` with interpolated strings is a small price to pay to avoid the first issue.
Jan 29 2021
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 29 January 2021 at 12:09:26 UTC, Dukc wrote:
 On Friday, 29 January 2021 at 11:09:57 UTC, Walter Bright wrote:
 Sure, but #DIP1027 did not require a GC or fiddling around. So 
 it's not like it's not possible.
You need to remember that perhaps the most important use case for string interpolation are string mixins. With DIP1027, one would have to write `mixin(i"foreach(i;0..${iterations}) foo()".format)` instead of `mixin(i"foreach(i;0..${iterations}) foo()")` that works with this proposal. Added `.format` for every single interpolated string mixin.
You cannot actually pass an i"..." literal directly to `mixin` with this proposal; you have to call `idup` (or some other function) to convert it to a string first.
Jan 29 2021
next sibling parent reply Dukc <ajieskola gmail.com> writes:
On Friday, 29 January 2021 at 12:41:35 UTC, Paul Backus wrote:
 You cannot actually pass an i"..." literal directly to `mixin` 
 with this proposal; you have to call `idup` (or some other 
 function) to convert it to a string first.
Why? I understood it like this: `mixin(i"foreach(i;0..${iterations}) foo();")` is first attempted as `mixin(Interp!"foreach(i;0.."(), iterations, Interp!") foo()"());`. Since `mixin` accepts only strings, `idup` is implicitly called on the arguments. What went wrong?
Jan 29 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 29 January 2021 at 13:03:45 UTC, Dukc wrote:
 On Friday, 29 January 2021 at 12:41:35 UTC, Paul Backus wrote:
 You cannot actually pass an i"..." literal directly to `mixin` 
 with this proposal; you have to call `idup` (or some other 
 function) to convert it to a string first.
Why? I understood it like this: `mixin(i"foreach(i;0..${iterations}) foo();")` is first attempted as `mixin(Interp!"foreach(i;0.."(), iterations, Interp!") foo()"());`. Since `mixin` accepts only strings, `idup` is implicitly called on the arguments. What went wrong?
mixin does not only accept strings: int n = mixin(1); assert(n == 1); From the language spec [1]:
 Each AssignExpression in the ArgumentList is evaluated at 
 compile
 time, and the result must be representable as a string.
In concrete terms, what happens is that mixin implicitly calls `.stringof` on each of its arguments. So your mixin example would be lowered to mixin(interp!"foreach(i;0.."().stringof, iterations.stringof, interp!") foo();"().stringof); And evaluating the `.stringof` calls would result in // assuming iterations == 5 mixin(`interp()`, `5`, `interp()`); ...which in this specific case happens to be a compile error, so it actually *would* be rewritten by the compiler to use `idup`. But there is no guarantee that will happen in general. [1] https://dlang.org/spec/expression.html#mixin_expressions
Jan 29 2021
parent reply Dukc <ajieskola gmail.com> writes:
On Friday, 29 January 2021 at 13:21:18 UTC, Paul Backus wrote:
 In concrete terms, what happens is that mixin implicitly calls 
 `.stringof` on each of its arguments. So your mixin example 
 would be lowered to

     mixin(interp!"foreach(i;0.."().stringof, 
 iterations.stringof, interp!") foo();"().stringof);

 And evaluating the `.stringof` calls would result in

     // assuming iterations == 5
     mixin(`interp()`, `5`, `interp()`);
Oops. I quess the DIP would do well to solve that somehow.
 ...which in this specific case happens to be a compile error, 
 so it actually *would* be rewritten by the compiler to use 
 `idup`..
Or perhaps not, since the compiler error would not result from arguments not being accepted.
Jan 29 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 29 January 2021 at 14:17:41 UTC, Dukc wrote:
 On Friday, 29 January 2021 at 13:21:18 UTC, Paul Backus wrote:
 In concrete terms, what happens is that mixin implicitly calls 
 `.stringof` on each of its arguments. So your mixin example 
 would be lowered to

     mixin(interp!"foreach(i;0.."().stringof, 
 iterations.stringof, interp!") foo();"().stringof);

 And evaluating the `.stringof` calls would result in

     // assuming iterations == 5
     mixin(`interp()`, `5`, `interp()`);
Oops. I quess the DIP would do well to solve that somehow.
 ...which in this specific case happens to be a compile error, 
 so it actually *would* be rewritten by the compiler to use 
 `idup`..
Or perhaps not, since the compiler error would not result from arguments not being accepted.
...unless you happen to have something else named `interp` in scope that makes the `.stringof` results compile. Have fun chasing down *that* bug. :)
Jan 29 2021
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 29 January 2021 at 14:21:39 UTC, Paul Backus wrote:
 ...unless you happen to have something else named `interp` in 
 scope that makes the `.stringof` results compile. Have fun 
 chasing down *that* bug. :)
What are you talking about? this DIP is pretty explicit about where interp lives.
Jan 29 2021
parent reply Dukc <ajieskola gmail.com> writes:
On Friday, 29 January 2021 at 14:30:03 UTC, Adam D. Ruppe wrote:
 On Friday, 29 January 2021 at 14:21:39 UTC, Paul Backus wrote:
 ...unless you happen to have something else named `interp` in 
 scope that makes the `.stringof` results compile. Have fun 
 chasing down *that* bug. :)
What are you talking about? this DIP is pretty explicit about where interp lives.
But you could define your own to the same module where the mixin lives. In that case, it would get called by the mixin, not `object.interp`.
Jan 29 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 9:54 AM, Dukc wrote:
 On Friday, 29 January 2021 at 14:30:03 UTC, Adam D. Ruppe wrote:
 On Friday, 29 January 2021 at 14:21:39 UTC, Paul Backus wrote:
 ...unless you happen to have something else named `interp` in scope 
 that makes the `.stringof` results compile. Have fun chasing down 
 *that* bug. :)
What are you talking about? this DIP is pretty explicit about where interp lives.
But you could define your own to the same module where the mixin lives. In that case, it would get called by the mixin, not `object.interp`.
The intent is for the compiler to identify the exact location of interp. This doesn't necessarily mean lowering, it means that only the druntime interp template will be used (which is easy to do internally). There will be no overriding of interp or idup allowed in user code. I could have sworn I had a statement in the DIP that said that, but I may have deleted it in the many rewrites. -Steve
Jan 29 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 29 January 2021 at 15:28:21 UTC, Steven Schveighoffer 
wrote:
 The intent is for the compiler to identify the exact location 
 of interp.

 This doesn't necessarily mean lowering, it means that only the 
 druntime interp template will be used (which is easy to do 
 internally).
Once the `interp` instance passes through the implicit .stringof in mixin(...), all of that semantic information is lost. Most of the time you will get lucky and it will work anyway, but you have no guarantee. Adam knows all about the pitfalls of mixin + stringof, so feel free to ask him about it if you still don't understand.
 There will be no overriding of interp or idup allowed in user 
 code. I could have sworn I had a statement in the DIP that said 
 that, but I may have deleted it in the many rewrites.
Unless you make `interp` and `idup` reserved identifiers (which is a breaking language change), there is no way you can prevent this in the general case.
Jan 29 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 10:49 AM, Paul Backus wrote:
 On Friday, 29 January 2021 at 15:28:21 UTC, Steven Schveighoffer wrote:
 The intent is for the compiler to identify the exact location of interp.

 This doesn't necessarily mean lowering, it means that only the 
 druntime interp template will be used (which is easy to do internally).
Once the `interp` instance passes through the implicit .stringof in mixin(...), all of that semantic information is lost. Most of the time you will get lucky and it will work anyway, but you have no guarantee.
mixin(i"int ${idname} = 5;"); will ALWAYS be rewritten to mixin(idup(i"int ${idname} = 5;")); If that is not clear in the DIP, it will be in the next update.
 Adam knows all about the pitfalls of mixin + stringof, so feel free to 
 ask him about it if you still don't understand.
I understand and it's not relevant.
 There will be no overriding of interp or idup allowed in user code. I 
 could have sworn I had a statement in the DIP that said that, but I 
 may have deleted it in the many rewrites.
Unless you make `interp` and `idup` reserved identifiers (which is a breaking language change), there is no way you can prevent this in the general case.
The compiler is building the instantiation of interp and idup, it can do whatever it wants to tell itself what it should do. The idea that they would need to be reserved identifiers is irrelevant, they don't appear in code. In fact, they can be __interp and __idup if you want, it doesn't really matter what they are called. It is true that an explicit idup call will have to deal with any local overrides. But that is true for idup in general. -Steve
Jan 29 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 29 January 2021 at 17:01:36 UTC, Steven Schveighoffer 
wrote:
 On 1/29/21 10:49 AM, Paul Backus wrote:
 Once the `interp` instance passes through the implicit 
 .stringof in mixin(...), all of that semantic information is 
 lost. Most of the time you will get lucky and it will work 
 anyway, but you have no guarantee.
mixin(i"int ${idname} = 5;"); will ALWAYS be rewritten to mixin(idup(i"int ${idname} = 5;")); If that is not clear in the DIP, it will be in the next update.
This directly contradicts what is written in the DIP, which is that if the tuple expansion compiles successfully, it is used, and the idup expansion is not attempted. If this is not what you intended, then the DIP needs to be revised to reflect that.
Jan 29 2021
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 12:16 PM, Paul Backus wrote:
 On Friday, 29 January 2021 at 17:01:36 UTC, Steven Schveighoffer wrote:
 On 1/29/21 10:49 AM, Paul Backus wrote:
 Once the `interp` instance passes through the implicit .stringof in 
 mixin(...), all of that semantic information is lost. Most of the 
 time you will get lucky and it will work anyway, but you have no 
 guarantee.
mixin(i"int ${idname} = 5;"); will ALWAYS be rewritten to mixin(idup(i"int ${idname} = 5;")); If that is not clear in the DIP, it will be in the next update.
This directly contradicts what is written in the DIP, which is that if the tuple expansion compiles successfully, it is used, and the idup expansion is not attempted. If this is not what you intended, then the DIP needs to be revised to reflect that.
Yes, I was not aware of the mixin parameter list acceptance. The intention was for it to convert to a string in that case. It will be fixed in an update as mentioned. The intention is for the expanded version only to be used in template or function arguments. -Steve
Jan 29 2021
prev sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 7:41 AM, Paul Backus wrote:
 On Friday, 29 January 2021 at 12:09:26 UTC, Dukc wrote:
 On Friday, 29 January 2021 at 11:09:57 UTC, Walter Bright wrote:
 Sure, but #DIP1027 did not require a GC or fiddling around. So it's 
 not like it's not possible.
You need to remember that perhaps the most important use case for string interpolation are string mixins. With DIP1027, one would have to write `mixin(i"foreach(i;0..${iterations}) foo()".format)` instead of `mixin(i"foreach(i;0..${iterations}) foo()")` that works with this proposal. Added `.format` for every single interpolated string mixin.
You cannot actually pass an i"..." literal directly to `mixin` with this proposal; you have to call `idup` (or some other function) to convert it to a string first.
This is not true. mixin(i"...") will work just fine (an implicit idup will be added). -Steve
Jan 29 2021
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 1/29/2021 4:09 AM, Dukc wrote:
 You need to remember that perhaps the most important use case
If it is the most important use case, the DIP should address that in the rationale and examples.
Jan 30 2021
prev sibling next sibling parent reply Kagamin <spam here.lot> writes:
auto convoluted = i"${ir"`${"{"}`"}"; // nested string 
interpolations work.
assert(convoluted == "`{`");

Whoa, this looks impossible to lex.
Jan 28 2021
parent Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 28 January 2021 at 20:37:46 UTC, Kagamin wrote:
 Whoa, this looks impossible to lex.
It's not. Please everyone, if you are going to leave a comment, leave a substantive explanation too.
Jan 28 2021
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
Comparing two simple examples of #DIP1036 and #DIP1027:

DIP1036:
   printf(i"%s${item} %02d${other_item}"); // metaprogramming + printf overload

DIP2027:
   printf(i"$item ${%02d}other_item");  // direct


DIP1036:
   writeln(i"I ate ${apples} apples and ${bananas} bananas totaling ${apples + 
bananas} fruit.");

DIP1027:
   writefln(i"I ate $apples apples and $bananas bananas totaling $(apples + 
bananas) fruit.");


DIP1036 is more user typing, including awkward-to-type { }, along with needing 
substantial user code to implement (for printf).

DIP1027's syntax is optimized to make the most common cases the simplest for
the 
user, no user code is required.
Jan 29 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 3:02 AM, Walter Bright wrote:
 Comparing two simple examples of #DIP1036 and #DIP1027:
 
 DIP1036:
    printf(i"%s${item} %02d${other_item}"); // metaprogramming + printf 
 overload
With a metaprogramming wrapper, it would be: printf(i"${item} %02d${other_item}"); Without a metaprogramming wrapper, you call it like this: printf("%s %02d", item, other_item); // yes, just use printf the way it was intended The point of this is, we don't want to fit into printf-style formatting, because it's too niche a need, and extremely limiting. Use the wrapper if you want printf formatting. This is not a burden on anyone (the wrapper took me 15 minutes to write, and is an inlineable no-processing call).
 
 DIP2027:
    printf(i"$item ${%02d}other_item");  // direct
This is an error, unless item is a const char * (unlikely).
 DIP1036:
    writeln(i"I ate ${apples} apples and ${bananas} bananas totaling 
 ${apples + bananas} fruit.");
 
 DIP1027:
    writefln(i"I ate $apples apples and $bananas bananas totaling 
 $(apples + bananas) fruit.");
 
 
 DIP1036 is more user typing, including awkward-to-type { }, along with 
 needing substantial user code to implement (for printf).
It is possible for the DIP to allow omission of the {}. We did not do this for several reasons: 1. Just a literal $ is frequently used in string data (it is a denomination, and has valid uses in mixin D code). 2. The requirement to type {} is not much of a burden. 3. Using enclosing tokens has precedent in many string interpolation implementations. Javascript uses this exact sequence. Swift uses \( ... 4. It's easier for a person to distinguish in a large string.
 
 DIP1027's syntax is optimized to make the most common cases the simplest 
 for the user, no user code is required.
DIP1027's syntax is designed to fit with a few existing functions. It puts the burden on the user to make sure they understand how the rewrite will happen, and which functions to use to do this. It is not forgiving of mistakes, which will compile and do the wrong thing. But I don't want to rehash DIP1027 here, that discussion has already happened. If you want to compare the two briefly, I would say DIP1027 designed for writef and format, nothing else. It can be used unintuitively with printf and other functions that have some similar parameter layouts. It can be used accidentally with many functions that happen to accept the parameters as arranged. DIP1036 is designed to be used by library writers to provide a mechanism to distinguish and handle properly interpolation strings ONLY when intended, and do it with little effort, all while also providing the user a mechanism to seamlessly convert normal data into string data. -Steve
Jan 29 2021
next sibling parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Friday, 29 January 2021 at 15:06:20 UTC, Steven Schveighoffer 
wrote:
 On 1/29/21 3:02 AM, Walter Bright wrote:
 [...]
With a metaprogramming wrapper, it would be: printf(i"${item} %02d${other_item}"); [...]
+1
Jan 29 2021
prev sibling next sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
On Friday, 29 January 2021 at 15:06:20 UTC, Steven Schveighoffer 
wrote:
 [snip]

 With a metaprogramming wrapper, it would be:

 printf(i"${item} %02d${other_item}");

 Without a metaprogramming wrapper, you call it like this:

 printf("%s %02d", item, other_item); // yes, just use printf 
 the way it was intended

 The point of this is, we don't want to fit into printf-style 
 formatting, because it's too niche a need, and extremely 
 limiting. Use the wrapper if you want printf formatting. This 
 is not a burden on anyone (the wrapper took me 15 minutes to 
 write, and is an inlineable no-processing call).
 [snip]
Is there any reason why it needs to be an overload of printf instead of a separate function, like say iprintf?
Jan 29 2021
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 10:26 AM, jmh530 wrote:
 On Friday, 29 January 2021 at 15:06:20 UTC, Steven Schveighoffer wrote:
 [snip]

 With a metaprogramming wrapper, it would be:

 printf(i"${item} %02d${other_item}");

 Without a metaprogramming wrapper, you call it like this:

 printf("%s %02d", item, other_item); // yes, just use printf the way 
 it was intended

 The point of this is, we don't want to fit into printf-style 
 formatting, because it's too niche a need, and extremely limiting. Use 
 the wrapper if you want printf formatting. This is not a burden on 
 anyone (the wrapper took me 15 minutes to write, and is an inlineable 
 no-processing call).
 [snip]
Is there any reason why it needs to be an overload of printf instead of a separate function, like say iprintf?
No reason at all. But it's possible if you want to (as I showed). The DIP provides the possibility for doing whatever you want, without accidentally doing what you don't want. -Steve
Jan 29 2021
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/29/2021 7:06 AM, Steven Schveighoffer wrote:
 The point of this is, we don't want to fit into printf-style formatting,
That is abundantly clear :-) But it is a test on how powerful the feature is and where its limits are.
 DIP1027's syntax is designed to fit with a few existing functions.
More accurately, it is designed to fit with an enormous amount of existing use. The printf format pattern is very common with other functions - dmd's source code has many examples of it. DIP1027 actually had zero knowledge of printf formatting other than %s being the default format, which is shared with writefln. This was very intentional in its design.
 It puts the 
 burden on the user to make sure they understand how the rewrite will happen,
and 
 which functions to use to do this.
The rewrite is far simpler than #DIP1036's is, and no functions are used to do it. Note how short DIP1027 is.
 It is not forgiving of mistakes, which will 
 compile and do the wrong thing.
Since D now does printf format checking, format mistakes will no longer compile. (We should have added that to D a decade ago! Dang I like that feature.)
 But I don't want to rehash DIP1027 here, that discussion has already happened.
Since #DIP1027 was rejected, it is necessary for #DIP1036 to be a substantial improvement over it, otherwise we just go sideways. Comparisons are fair.
 If you want to compare the two briefly, I would say DIP1027 designed for
writef 
 and format, nothing else. It can be used unintuitively with printf and other 
 functions that have some similar parameter layouts. It can be used
accidentally 
 with many functions that happen to accept the parameters as arranged.
You and I strongly disagree on that assessment.
 DIP1036 is 
 designed to be used by library writers to provide a mechanism to distinguish
and 
 handle properly interpolation strings ONLY when intended,
The fallback to matching string parameters doesn't fit that, and isn't different from DIP1027 in that regard.
 and do it with little 
 effort, all while also providing the user a mechanism to seamlessly convert 
 normal data into string data.
It pains me to say this, but would I use #DIP1036 in my own code? No. It adds too many layers of abstraction, is hard to document, hard to remember, adds special new rules for overloading, is unclear when it uses the GC, I have to write wrappers to use it, and the user-facing part just doesn't look good. DIP1027 was a simple lexical rewrite - easy to remember, easy to document, sensible defaults, no allocations, no function calls, no overloading, no wrappers. It wasn't perfect, but simplicity and predictability are huge advantages. P.S. I used to write macros in C named "printf" that would muck about with the arguments, add some logic, then call the real printf. After a few years I got tired of them, and put them in a bag with some rocks and threw it in the swamp.
Jan 30 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/31/21 1:24 AM, Walter Bright wrote:
 On 1/29/2021 7:06 AM, Steven Schveighoffer wrote:
 The point of this is, we don't want to fit into printf-style formatting,
That is abundantly clear :-) But it is a test on how powerful the feature is and where its limits are.
Yes, and it passes the test quite clearly -- it is possible to write a formatting function with this DIP.
 DIP1027's syntax is designed to fit with a few existing functions.
More accurately, it is designed to fit with an enormous amount of existing use. The printf format pattern is very common with other functions - dmd's source code has many examples of it.
But it does not work with printf for strings. Only writef and format provide full compatibility. And beside the point, this is not a feature that is only to be used by DMD and it's style of code. The "parse a string to figure out what to do" is vastly inferior to introspection. Making decisions on what to do and how to do it at compile time is D's specialty, why should we give up that power for a crippled runtime string (which can be manipulated at will by the function caller)?
 DIP1027 actually had zero knowledge of printf formatting other than %s 
 being the default format, which is shared with writefln. This was very 
 intentional in its design.
%s is wrong for all standard D types when talking about printf. DIP1027 works with writef, and I consider that to be the goal, not printf.
 
 
 It puts the burden on the user to make sure they understand how the 
 rewrite will happen, and which functions to use to do this.
The rewrite is far simpler than #DIP1036's is, and no functions are used to do it. Note how short DIP1027 is.
How is the rewrite simpler? DIP1036 does not have to parse format specifiers. It produces arguments intuitively in the order provided instead of rearranging all of them and providing a runtime-only-accessible blueprint on what to do with them.
 It is not forgiving of mistakes, which will compile and do the wrong 
 thing.
Since D now does printf format checking, format mistakes will no longer compile. (We should have added that to D a decade ago! Dang I like that feature.)
You know who doesn't care about that feature? The 99% of D developers that don't use printf. You once said that Java IDEs that provide shortcuts to write boilerplate for you was a failure compared to templates. I'm saying that same thing now. This does not help with any other kind of formatting. It doesn't help with writef. It doesn't help with mysql. It's a very specific feature. The D language is not made to cater to printf. And if DIP1027 is an example of that, it's a poor example as it doesn't work with strings.
 
 
 But I don't want to rehash DIP1027 here, that discussion has already 
 happened.
Since #DIP1027 was rejected, it is necessary for #DIP1036 to be a substantial improvement over it, otherwise we just go sideways. Comparisons are fair.
DIP1027 and DIP1036 have substantially different goals. DIP1027 is a mechanism to make calls to format-blueprint style functions possible to write in a different way. DIP1036 intends to make it possible to hook string interpolation, prevent incorrect usage, and if not, just convert it to a string. But if you want to compare them, I can give you a list: * printf DIP1036: Not usable out of the gate. Works with wrapper, which can provides introspection (no formatting specifiers needed, but are supported). DIP1027: Works with some types, but not strings. Provides no introspection. Easy to get wrong (though special compiler magic can prevent some errors). No overloads possible to correct these deficiencies. * writef / format DIP1036: Can be used, but will convert to a string first. With an overload, full support for everything with compile-time error checking of formats. DIP1027: Works as long as you don't insert extra or incorrect format specifiers, and as long as the interpolation string is the first parameter, and there are no other parameters. No compiler magic to check format specifiers. No overloads possible to correct these deficiencies. * sql "printf style" functions DIP1036: Can be used as a string only out of the gate. With an additional overload, can provide a rich intuitive experience (see DIP for examples). DIP1027: All placeholders must be spelled out, no introspection of data to form SQL query. No compiler magic to check for proper placeholders. Format placeholders not checkable at compile time. No overloads possible to correct these deficiencies. * string assignment/argument DIP1036: done automatically, or explicitly with a minimal druntime function. Or one may use std.conv.text directly. DIP1027: available with a call to format with Phobos. No compiler magic verification of formatting specification - only checkable at runtime. No overload possible to correct these deficiencies. Compare usage: DIP1027 printf(i"Hello, %.*s${}(cast(int)name.length)${}(name.ptr), how are you today?\n"); DIP1036 iprintf(i"Hello, ${name}, how are you today?\n"); // requires shim that is betterC compatible DIP1027 writef(i"Hex value of x is ${%x}(x)"); DIP1036 writef(i"Hex value of x is %x${x}"); // requires overload DIP1027 writeln(i"Hello, $name"); DIP1036 writeln(i"Hello, ${name}"); DIP1027 mysql_exec("UPDATE tbl SET col1=${?}(col1), col2=${?}(col2), col3=${?}(col3) WHERE id = ${?}(id)"); DIP1036 mysql_exec("UPDATE tbl SET col1=${col1}, col2=${col2}, col3=${col3} WHERE id = ${id}"); // requires overload DIP1027 throw new Exception(i"Error, name and age are not valid (name = $name, age = $age)"); DIP1036 throw new Exception(i"Error, name and age are not valid (name = ${name}, age = ${age})"); Bonus: all of the above would compile with DIP1027. Spot the runtime issues with them.
 
 DIP1036 is designed to be used by library writers to provide a 
 mechanism to distinguish and handle properly interpolation strings 
 ONLY when intended,
The fallback to matching string parameters doesn't fit that, and isn't different from DIP1027 in that regard.
The idup rewrite allows usage of string interpolations as strings where library writers have not written overloads to accept the expanded form. It doesn't change the fact that library writers are *provided* a mechanism. In other words, the expanded form shouldn't match where it wasn't intended. And the string form is usable by users where strings are intended. DIP1027 is different in that it uses common existing types that match many existing functions (even ones that are not intended to).
 and do it with little effort, all while also providing the user a 
 mechanism to seamlessly convert normal data into string data.
It pains me to say this, but would I use #DIP1036 in my own code? No. It adds too many layers of abstraction, is hard to document, hard to remember, adds special new rules for overloading, is unclear when it uses the GC, I have to write wrappers to use it, and the user-facing part just doesn't look good.
So you would prefer: string s = format("%s: %s", name, val); over string s = i"${name}: ${val}"; It's easy to know when the GC is used. If it makes a string, the GC is used. If not, the GC is not (unless the function accepting the parameters does it). How do you know whether a function accepts it as the expanded form? you read the documentation. Do you have to write wrappers to use it? No. You can use it purely as a string builder.
 DIP1027 was a simple lexical rewrite - easy to remember, easy to 
 document, sensible defaults, no allocations, no function calls, no 
 overloading, no wrappers. It wasn't perfect, but simplicity and 
 predictability are huge advantages.
It is simple, unintuitive (the parameters are rearranged, placeholder defaults have nothing to do with which function is called), and usable only in a select few functions (which does not include printf IMO). Writing proper functions to accept them takes a perfectly usable ordering of string and expression data, and hides it behind a runtime-only accessible string. One which the user can override anywhere he wants, and only with printf does he get a compiler error for incorrect specifiers. This is a very poor mechansim for a language built on top of introspection and metaprogramming.
 P.S. I used to write macros in C named "printf" that would muck about 
 with the arguments, add some logic, then call the real printf. After a 
 few years I got tired of them, and put them in a bag with some rocks and 
 threw it in the swamp.
I'm not sure we can get past this if you keep ascribing completely irrelevant anecdotes to this DIP. The printf shim I wrote is not *even close* to a C macro. And it's completely unneccesary for this DIP's acceptance. This DIP is not aimed at printf *at all*. -Steve
Jan 31 2021
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
 Try to please everybody and you end up pleasing nobody. 
...
 DIP1027 works with writef, and I consider that to be the goal, not printf.
Unpopular opinion follows (at this point I have no idea which side of the debate this puts me on). I think working with either writef or printf is a bit of a side quest, and to the extent it affects the fundamental design it becomes a nongoal. The printf family (which includes writef, too) has a fundamental characteristic: the formatting specification are SEPARATE from the data. You pass the formatting template, you pass the data, and the function puts them together and produces a string nice. This way of doing things has pluses and minuses that are well known. Interpolated strings fundamentally do the converse: they MIX the formatting specification with the data. You see bits of literal strings mixed with pieces of data. That, too, has pluses and minuses. From that vantage point, I look at the effort of supporting {print,write}f in interpolated strings leading to any number of contortions and come and ask: cui prodest? Indeed, cui the heck prodest? The main client of string interpolation by A MILE, is code generation. Whether it's D code, SQL, your own embedded language - that's the ONE use case that any DIP proposing this should drive with. Supporting (print|write)f is some weird invention - I already have these functions that separate formatting from data, why do I want to transform the converse way of doing things into this? But wait! one might say in response. What if I need on occasion some special formatting of one argument in one of them nice interpolated strings? No problem, I say. Do what Perl, PHP, and Python programmers have done for millenia - apply the formatting function separately for the desired argument: i"Hello, ${format(`%45s`, var)} world"! That's how the cookie crumbles. To have to pay syntactically and in complexity WHENEVER I EVER use an interpolated string on the off chance that I might need special formatting for ONE argument is unconscionable. Define interpolated strings to be simple, cheap, obvious, and well fit for their MAIN USE CASE, which is code generation.
Jan 31 2021
next sibling parent Max Haughton <maxhaton gmail.com> writes:
On Sunday, 31 January 2021 at 15:54:38 UTC, Andrei Alexandrescu 
wrote:
 Try to please everybody and you end up pleasing nobody.
...
 DIP1027 works with writef, and I consider that to be the goal, 
 not printf.
Unpopular opinion follows (at this point I have no idea which side of the debate this puts me on). I think working with either writef or printf is a bit of a side quest, and to the extent it affects the fundamental design it becomes a nongoal. The printf family (which includes writef, too) has a fundamental characteristic: the formatting specification are SEPARATE from the data. You pass the formatting template, you pass the data, and the function puts them together and produces a string nice. This way of doing things has pluses and minuses that are well known. Interpolated strings fundamentally do the converse: they MIX the formatting specification with the data. You see bits of literal strings mixed with pieces of data. That, too, has pluses and minuses. From that vantage point, I look at the effort of supporting {print,write}f in interpolated strings leading to any number of contortions and come and ask: cui prodest? Indeed, cui the heck prodest? The main client of string interpolation by A MILE, is code generation. Whether it's D code, SQL, your own embedded language - that's the ONE use case that any DIP proposing this should drive with. Supporting (print|write)f is some weird invention - I already have these functions that separate formatting from data, why do I want to transform the converse way of doing things into this? But wait! one might say in response. What if I need on occasion some special formatting of one argument in one of them nice interpolated strings? No problem, I say. Do what Perl, PHP, and Python programmers have done for millenia - apply the formatting function separately for the desired argument: i"Hello, ${format(`%45s`, var)} world"! That's how the cookie crumbles. To have to pay syntactically and in complexity WHENEVER I EVER use an interpolated string on the off chance that I might need special formatting for ONE argument is unconscionable. Define interpolated strings to be simple, cheap, obvious, and well fit for their MAIN USE CASE, which is code generation.
Isn't the specific formatting for a given argument mainly for things like floats in practice? We have good ways of doing this already, I think if any given proposal is merged it seems worth holding back and collecting experience before standardising anything other than calling format yourself (What alternatives do you have in mind? - the solution I see in python looks fairly arcane) But I (intensely) agree that printf really doesn't matter here - even talking C, puts surely is the closest analogue.
Jan 31 2021
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/31/21 10:54 AM, Andrei Alexandrescu wrote:
 Try to please everybody and you end up pleasing nobody. 
...
 DIP1027 works with writef, and I consider that to be the goal, not 
 printf.
Unpopular opinion follows (at this point I have no idea which side of the debate this puts me on).
I don't think it's unpopular, or wrong. I 100% agree with your assessment.
 But wait! one might say in response. What if I need on occasion some 
 special formatting of one argument in one of them nice interpolated 
 strings? No problem, I say. Do what Perl, PHP, and Python programmers 
 have done for millenia - apply the formatting function separately for 
 the desired argument:
 
 i"Hello, ${format(`%45s`, var)} world"!
This is how I would do it: i"Hello, ${var.withFormat!"%45s"} world"; (where withFormat provides a toString overload that works with idup). No extra intermediate allocations necessary. -Steve
Jan 31 2021
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 1/31/21 11:11 AM, Steven Schveighoffer wrote:
 This is how I would do it:
 
 i"Hello, ${var.withFormat!"%45s"} world";
 
 (where withFormat provides a toString overload that works with idup). No 
 extra intermediate allocations necessary.
I wonder if we can define a nice convention by the use of past tense vs. active. format!"%45s"(val) -> format using this spec the following values val.formatted!"%45s" -> value formatted with this spec We have just a bit of that with sort/sorted. Maybe flesh that up in phobos 2.0 :o).
Jan 31 2021
prev sibling next sibling parent reply Arafel <er.krali gmail.com> writes:
Replying here to Walter's post in the Feedback Thread:

https://forum.dlang.org/post/rv0grf$g1j$1 digitalmars.com

 Let's compare mysql:
 
 DIP1036:
 
 mysql_query(i"select * from foo where id = ${obj.id} and val > ${minval}");
 
 DIP1027:
 
 mysql_query(i"select * from foo where id = ${?}(obj.id) and val > ${?}minval");
 
 The DIP1027 could be shorter or longer, depending on if the ( ) were needed or
not. DIP1036 also requires a user-written overload for mysql_query(), DIP1027
does not. DIP1036 is not a clear winner for mysql. 
Perhaps DIP1027 makes life easier for the library developer. As a _user_, however, DIP1036 wins by a landslide, because the placeholder is an internal implementation detail that needn't and shouldn't be exposed. In fact, different SQL "dialects" can use different placeholders, so DIP1036 allows the following abstraction: ``` auto preparedStatement = connection.prepare(i"select * from foo where id = ${obj.id} and val > ${minval}") ``` This will then be translated by the actual class implementing the Connection interface to the proper version: Postgres: ``` postgres_prepare("select * from foo where id = $1 and val > $2", obj.id, minval); ``` MySQL: ``` mysql_prepare("select * from foo where id = ? and val > ?", obj.id, minval); ``` Furthermore, the Postgres syntax becomes almost useless with DIP1027 if the user still has to keep track of the position of each argument, and for instance update all of them when adding one at the beginning. As the user of a library, if I use an interpolated string, that's exactly the kind of thing I expect it to manage automatically for me. Of course one could settle on a default placeholder and then reparse the string to adapt it as needed, that's in fact what JDBC does, but... here we have the chance to do the right thing from the beginning. Finally, although a bit secondary, I personally find that the syntax in DIP1027 is somewhat awkward: If I see `${?}minval`, or even `${?}(obj.id)` I intuitively expect only `?` to be part of the interpolation, that's how it works in many languages (bash, PERL) when a variable is expressed as `${...}`, it specifically makes clear that everything out of the brackets is not included.
Jan 29 2021
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/29/2021 2:53 AM, Arafel wrote:
 Of course one could settle on a default placeholder and then reparse the
string 
 to adapt it as needed, that's in fact what JDBC does, but... here we have the 
 chance to do the right thing from the beginning.
I bet it could be done with a template overload of the function call. This doesn't make it worse than DIP1036, which requires template overloads written by the user anyway.
Jan 29 2021
next sibling parent Arafel <er.krali gmail.com> writes:
On 29/1/21 12:14, Walter Bright wrote:
 On 1/29/2021 2:53 AM, Arafel wrote:
 Of course one could settle on a default placeholder and then reparse 
 the string to adapt it as needed, that's in fact what JDBC does, 
 but... here we have the chance to do the right thing from the beginning.
I bet it could be done with a template overload of the function call. This doesn't make it worse than DIP1036, which requires template overloads written by the user anyway.
You seem to refer to the library author as the "user", for me the "user" is the actual user of the library, and I'd say this kind of user is much more important (if only because there are more of them). It is them who should have an easy time, even if it requires a bit more effort from the library author. And I'm not sure how a template overload could possibly work with interfaces. The database driver often needs to be chosen at runtime, so you get something like this: ``` string dbDriver = readConfig("dbDriver"); auto connection = Driver.getDriver(dbDriver).getConnection(/* connection parameters * /); auto result = connection.execute(i"select * from foo where id = ${obj.id} and val > ${minval}"); // Needn't care about the internals ``` That's how it should look like for the end user. I don't see how the syntax of your example of DIP1027 could be easier than this for the real end-user... and not even for the author of the library. Just for completion, this is how the SQL library might look like (shameless copy of the structure of JDBC): ``` class Driver { static public Driver getDriver(string name) { /* ... */ } static public void register(Driver driver, string name) { /* ... */ } // To be called by each driver in `shared static this` abstract public Connection getConnection( /* ... */ ); } interface Connection { // ... public Result execute(/* ... */); } interface Result { /* ... */ } class MySQLConnection : Connection { // ... override public Result execute(/* ... */) { /* uses msql_query(...) and `?` */ } } class PostgreSQLConnection : Connection { // ... override public Result execute(/* ... */) { /* uses postgres_query(...) and `$1`, `$2`, etc. */ } } ```
Jan 29 2021
prev sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 6:14 AM, Walter Bright wrote:
 On 1/29/2021 2:53 AM, Arafel wrote:
 Of course one could settle on a default placeholder and then reparse 
 the string to adapt it as needed, that's in fact what JDBC does, 
 but... here we have the chance to do the right thing from the beginning.
I bet it could be done with a template overload of the function call. This doesn't make it worse than DIP1036, which requires template overloads written by the user anyway.
No it cannot (for DIP1027). Because the function that accepts a string format already exists. This was my main complaint of 1027 and why we wrote a new DIP. -Steve
Jan 29 2021
prev sibling next sibling parent reply Dukc <ajieskola gmail.com> writes:
On Thursday, 28 January 2021 at 14:58:36 UTC, Steven 
Schveighoffer at the feedback theard wrote:
 On 1/28/21 3:35 AM, Dukc wrote:
 The DIP states that foo(i"a:${a}, ${b}.") is rewritten as 
 `foo(Interp!"a:", a, Interp!", ", b, Interp!".")`. It think 
 it's better to rewrite it as `foo(Interp!"a:", 
 Interp!typeof(a)(a), Interp!", ", Interp!typeof(b)(b), 
 Interp!".")`. That way, `foo` has easier time introspecting 
 which came from the interpolated string.
First, I don't think it's critical for overloading, and will simply add to the template bloat. What are you going to do differently with `a` than you would with `Interp!(typeof(a))(a)`?
I was mainly thinking that I'd have easier time differentiating between an `int` in interpolated string and `int` passed before/after the interpolated string. And I have a type that will implicitly convert to string if I want to do that - no need to call `to!string(a)` or `a.Interp!(typeof(a))` first.
 The parameters are guaranteed to start and end with an 
 InterpolationLiteral, so one can assume that non-literal 
 arguments are interspersed inside the literal.
It can be done, but it sounds more complex for the introspecting function. I'm not strict about this though, what the DIP now proposes be worth it to be able to pass `ref` parameters in interpolated strings.
 The type of interpolated string literal is very special cased. 
 [snip]
I was fully aware that this would be the most controversial part. I feel like it will not be full of corner cases, but I'm not sure. Can you specify any? Consider a normal string literal can be used as a string, immutable(char)*, wstring, or dstring. I find it very similar to this feature, and I don't feel like there are a lot of corner cases there.
A string literal is a string that is implicitly assignable to the other alternatives via value range propagation mechanics, or that's how I understand it at least. The compromise that the interpolated string would be an expanded tuple, that would be implicitly assignable to string via value range propagation mechanics, sounds acceptable. But it needs to be clear IMO what the primary type of an interpolated string is. If it is not an expanded tuple, what it is then? I mean that this must be guaranteed to pass IMO: ``` auto interpolation = i"&{apples} apples and ${oranges} oranges"; static pure int foo(typeof(interpolation)); static void foo(string){assert(0,"this must not be called");} assert(foo(interpolation) == foo(i"&{apples} apples and ${oranges} oranges")); ```
 Let me suggest an alternative: [snip]
 
We have considered that. The problem is that people will use the string interpolation form without realizing the dangers or resulting bloat. For instance, writeln(i"Hello, ${name}"), if made to proactively generate a string just to send it to writeln is extremely wasteful when writeln(I"Hello, ${name}") is not.
I don't think it's that bad, we tend to do stuff like `writeln("hello, " ~ name)` anyway. Relatively small inefficiencies like this don't matter in non-critical places, and critical places need to be benchmarked and optimized anyway. Or did you mean template bloat? From that perspective I don't see difference. The first case will cause a new `idup` instance that interprets `name`, the second will cause a `writeln` instance that has similar interpretation mechanics.
 Consider also that code which uses a dual-literal system might 
 have to use the string interpolation form because the library 
 only allows that. Then at some point in the future, the library 
 adds support for the expanded form. Now the user would have to 
 go back and switch all usage to that new form, whereas an 
 auto-rewrite would just work without changes.
True, but the user can just continue to use the old form. If it was good enough until then, why hurry switching? Besides, the migration path is very easy.
Jan 29 2021
next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 7:58 AM, Dukc wrote:
 On Thursday, 28 January 2021 at 14:58:36 UTC, Steven Schveighoffer at 
 the feedback theard wrote:
 On 1/28/21 3:35 AM, Dukc wrote:
 The DIP states that foo(i"a:${a}, ${b}.") is rewritten as 
 `foo(Interp!"a:", a, Interp!", ", b, Interp!".")`. It think it's 
 better to rewrite it as `foo(Interp!"a:", Interp!typeof(a)(a), 
 Interp!", ", Interp!typeof(b)(b), Interp!".")`. That way, `foo` has 
 easier time introspecting which came from the interpolated string.
First, I don't think it's critical for overloading, and will simply add to the template bloat. What are you going to do differently with `a` than you would with `Interp!(typeof(a))(a)`?
I was mainly thinking that I'd have easier time differentiating between an `int` in interpolated string and `int` passed before/after the interpolated string.
We debated whether one should be able to figure out the original interpolation string from the parameters. I don't think it's necessary, and adds unnecessary complexity. Just passing the expression data directly makes things easy to deal with instead of adding an extra type to deal with. If you can come up with a reasonable use case for differentiating, we can discuss.
 And I have a type that will implicitly convert to 
 string if I want to do that - no need to call `to!string(a)` or 
 `a.Interp!(typeof(a))` first.
I'm not sure what you mean.
 
 The parameters are guaranteed to start and end with an 
 InterpolationLiteral, so one can assume that non-literal arguments are 
 interspersed inside the literal.
It can be done, but it sounds more complex for the introspecting function. I'm not strict about this though, what the DIP now proposes be worth it to be able to pass `ref` parameters in interpolated strings.
It would help to discuss the benefits and drawbacks of both ideas if we had a concrete example of why you would want to differentiate.
 
 The type of interpolated string literal is very special cased. [snip]
I was fully aware that this would be the most controversial part. I feel like it will not be full of corner cases, but I'm not sure. Can you specify any? Consider a normal string literal can be used as a string, immutable(char)*, wstring, or dstring. I find it very similar to this feature, and I don't feel like there are a lot of corner cases there.
A string literal is a string that is implicitly assignable to the other alternatives via value range propagation mechanics, or that's how I understand it at least.
No, this isn't range-value propagation. There is no way to recreate or save the type that is a string literal. It's like null, where it can be assigned to any number of things, and how you use it tells the compiler what to do with it. It may be considered an implicit conversion. D has, however, added things like typeof(null), which still work as polysemous values (assignable to multiple types). Perhaps there is room to make auto a = i"..."; be an unnamed internal type, but I don't know if it's worth it for this DIP to get into that. It certainly is something out of my area, so I wouldn't begin to know how to write that DIP.
 The compromise that the interpolated string would be an expanded tuple, 
 that would be implicitly assignable to string via value range 
 propagation mechanics, sounds acceptable. But it needs to be clear IMO 
 what the primary type of an interpolated string is. If it is not an 
 expanded tuple, what it is then? 
As identified in the DIP, there is no "type", it's a sequence of expressions. It's not a value tuple (unless you wrap it in a function). This is on purpose, so it knows when to convert to a string.
 I mean that this must be guaranteed to 
 pass IMO:
 
 ```
 auto interpolation = i"&{apples} apples and ${oranges} oranges";
 
 static pure int foo(typeof(interpolation));
 static void foo(string){assert(0,"this must not be called");}
 
 assert(foo(interpolation) == foo(i"&{apples} apples and ${oranges} 
 oranges"));
 ```
No, that will not pass, and is guaranteed not to pass. typeof(interpolation) is string. Just like this wouldn't pass: auto x = 1, 2; int foo(int, int); foo(x); The compiler wouldn't allow it, and would rewrite with idup, yielding a string. The only way I could think it could work is if you made x an alias. We did not provide any mechanism for that, however.
 Let me suggest an alternative: [snip]
We have considered that. The problem is that people will use the string interpolation form without realizing the dangers or resulting bloat. For instance, writeln(i"Hello, ${name}"), if made to proactively generate a string just to send it to writeln is extremely wasteful when writeln(I"Hello, ${name}") is not.
I don't think it's that bad, we tend to do stuff like `writeln("hello, " ~ name)` anyway. Relatively small inefficiencies like this don't matter in non-critical places, and critical places need to be benchmarked and optimized anyway.
I don't consider it a small inefficiency to involve all of the machinery of generating a string just to throw it away after printing. But in any case, it's unnecessary without good reason.
 
 Or did you mean template bloat? From that perspective I don't see 
 difference. The first case will cause a new `idup` instance that 
 interprets `name`, the second will cause a `writeln` instance that has 
 similar interpretation mechanics.
No, I meant runtime bloat.
 
 Consider also that code which uses a dual-literal system might have to 
 use the string interpolation form because the library only allows 
 that. Then at some point in the future, the library adds support for 
 the expanded form. Now the user would have to go back and switch all 
 usage to that new form, whereas an auto-rewrite would just work 
 without changes.
True, but the user can just continue to use the old form. If it was good enough until then, why hurry switching? Besides, the migration path is very easy.
Consider that the user first tried i"...." and it didn't work, so they use I"..." and never touch it again, and never learn it again. They are just passing some stringy data to a function. Why should they have to go back and change it? Especially when it wouldn't accept what they originally wrote? Not only that, but maybe they don't even notice it now takes the interpolation form, so they continue to use the I"..." version when they really would have done i"..." if it had accepted it. It wasn't that it was "good enough", it was "there was no other way". I'd much rather have the library just change what it's doing, and work better than require me to go back and revisit all my code. And now I don't have to learn a new way of doing things. -Steve
Jan 29 2021
next sibling parent reply Dukc <ajieskola gmail.com> writes:
On Friday, 29 January 2021 at 19:10:55 UTC, Steven Schveighoffer 
wrote:
 On 1/29/21 7:58 AM, Dukc wrote:
 A string literal is a string that is implicitly assignable to 
 the other alternatives via value range propagation mechanics, 
 or that's how I understand it at least.
No, this isn't range-value propagation. There is no way to recreate or save the type that is a string literal.
It may be that VRP is not the correct term, but I meant that a string literal (just like an array literal, or VRPed integers) has one unambiguous primary type, that is used if it's not immediately assigned to something else.
 D has, however, added things like typeof(null), which still 
 work as polysemous values (assignable to multiple types).
But even there: it has a primary type that is tried first, before any conversion rules kick in. Unlike what you're proposing.
 I mean that this must be guaranteed to pass IMO:
 
 [snip]
No, that will not pass, and is guaranteed not to pass. typeof(interpolation) is string. Just like this wouldn't pass: auto x = 1, 2; int foo(int, int); foo(x); The compiler wouldn't allow it, and would rewrite with idup, yielding a string.
You need to specify the rules about the behaviour of the expression unambiguously, and that is going to be a lot harder if you don't allow yourself the luxury of using a primary type. If I understood what you're proposing, the expanded form is attempted only when the interpolated string is an argument to a function or a template. But this still leaves a lot of questions: 1: Is the expanded form attempted inside constructors? 2: Is the expanded form attempted if the interpolated string is passed as first argument in UFCS style? 3: What a variable with `enum` storage class stores an interpolated string and that gets passed to a function? 4: What does this template do when called with interpolated string? `auto foo(T...)(T arg){bar(arg);}` 5: If an interpolated string gets called by an operator, what happens? 6: Probably much more issues like these.
 I don't think it's that bad, we tend to do stuff like 
 `writeln("hello, " ~ name)` anyway. Relatively small 
 inefficiencies like this don't matter in non-critical places, 
 and critical places need to be benchmarked and optimized 
 anyway.
I don't consider it a small inefficiency to involve all of the machinery of generating a string just to throw it away after printing. But in any case, it's unnecessary without good reason.
The reason would be to have an unambiguous type for the string literal. I'd much rather have inefficiencies like the one mentioned, that I can manually optimise away if needed, than complex language rules that are likely to result in implementation bugs.
Jan 29 2021
next sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 5:39 PM, Dukc wrote:
 On Friday, 29 January 2021 at 19:10:55 UTC, Steven Schveighoffer wrote:
 On 1/29/21 7:58 AM, Dukc wrote:
 A string literal is a string that is implicitly assignable to the 
 other alternatives via value range propagation mechanics, or that's 
 how I understand it at least.
No, this isn't range-value propagation. There is no way to recreate or save the type that is a string literal.
It may be that VRP is not the correct term, but I meant that a string literal (just like an array literal, or VRPed integers) has one unambiguous primary type, that is used if it's not immediately assigned to something else.
This is mostly the same as that. The difference is in when you try things outside of the world of function calls, it uses the conversion unconditionally. This includes mixin, typeof, auto variables. It's slightly different from normal rules, and I'm very much interested in hearing ways this will break, but I also am still optimistic it is valid.
 
 D has, however, added things like typeof(null), which still work as 
 polysemous values (assignable to multiple types).
But even there: it has a primary type that is tried first, before any conversion rules kick in. Unlike what you're proposing.
Right, the preferred type isn't really a type. Which is what makes it strange in most cases where you would expect to see the results from the preferred type (i.e. typeof, auto variables). But I still think it works, because of the narrow scope of the expanded form usage. For almost all intents and purposes, the thing is a string, unless you accept it as a sequence. If this DIP goes down, a possibility is to use a different mechanism to ask for the expanded form other than providing a parameter list that matches. I just like the parameter list matching because it's simple and already understood.
 
 I mean that this must be guaranteed to pass IMO:

 [snip]
No, that will not pass, and is guaranteed not to pass. typeof(interpolation) is string. Just like this wouldn't pass: auto x = 1, 2; int foo(int, int); foo(x); The compiler wouldn't allow it, and would rewrite with idup, yielding a string.
You need to specify the rules about the behaviour of the expression unambiguously, and that is going to be a lot harder if you don't allow yourself the luxury of using a primary type.\ If I understood what you're proposing, the expanded form is attempted only when the interpolated string is an argument to a function or a template. But this still leaves a lot of questions: 1: Is the expanded form attempted inside constructors?
The expanded form is attempted when calling a constructor, it's just a function call. So new Foo(i"...") attempts the expanded form first, and if it does not match, uses the idup rewrite.
 2: Is the expanded form attempted if the interpolated string is passed 
 as first argument in UFCS style?
UFCS works by putting arguments on the left side of the dot first, so we are counting on it working with the expanded form. Otherwise, something like i"hello, ${name}".idup will not work correctly.
 3: What a variable with `enum` storage class stores an interpolated 
 string and that gets passed to a function?
It would be a string type, just like the auto storage class.
 4: What does this template do when called with interpolated string? 
 `auto foo(T...)(T arg){bar(arg);}`
foo would receive the expanded form, bar would only work if it accepted the expanded form (no rewrite is done there). Once the compiler decides not to rewrite into a string, it's a tuple for the duration. Again, this is not a type that implicitly converts, it's a rewrite by the compiler. This is similar to: void bar(const char *); foo(T)(T arg) { bar(arg); } bar("hello"); // ok foo("hello"); // error
 5: If an interpolated string gets called by an operator, what happens?
do you mean something like opAssign(i"...")? It should work the same as other function calls.
 6: Probably much more issues like these.
The rules are pretty straightforward, so I'm happy to tell you the answers.
 
 I don't think it's that bad, we tend to do stuff like 
 `writeln("hello, " ~ name)` anyway. Relatively small inefficiencies 
 like this don't matter in non-critical places, and critical places 
 need to be benchmarked and optimized anyway.
I don't consider it a small inefficiency to involve all of the machinery of generating a string just to throw it away after printing. But in any case, it's unnecessary without good reason.
The reason would be to have an unambiguous type for the string literal. I'd much rather have inefficiencies like the one mentioned, that I can manually optimise away if needed, than complex language rules that are likely to result in implementation bugs.
If there are implementation bugs, we can deal with them. If there are design problems, I want to figure them out now. So please, continue to try and figure out what could be wrong with this scheme! To put it another way, in the case where the "dual-mode" interpolation literals work and my rewrite scheme work, I'd prefer the rewrite scheme. If there is a killer problem which makes the rewrite scheme non-viable, then we have to consider other options, including dropping the rewrite. -Steve
Jan 29 2021
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 5:39 PM, Dukc wrote:
 On Friday, 29 January 2021 at 19:10:55 UTC, Steven Schveighoffer wrote:
 On 1/29/21 7:58 AM, Dukc wrote:
 A string literal is a string that is implicitly assignable to the 
 other alternatives via value range propagation mechanics, or that's 
 how I understand it at least.
No, this isn't range-value propagation. There is no way to recreate or save the type that is a string literal.
It may be that VRP is not the correct term, but I meant that a string literal (just like an array literal, or VRPed integers) has one unambiguous primary type, that is used if it's not immediately assigned to something else.
 D has, however, added things like typeof(null), which still work as 
 polysemous values (assignable to multiple types).
But even there: it has a primary type that is tried first, before any conversion rules kick in. Unlike what you're proposing.
I think we're going to end up with something like this. The more I think about it, the more I feel that having a primary type be the tuple is required. When I wrote that typeof(i"...") is string, that bothered me, even though it's how I had pictured it. The thing that is really bad is: pragma(msg, typeof(i"hello, ${name}")); // string void foo(T...)(T args) { pragma(msg, T); } foo(i"hello, ${name}"); // tuple(...) This just can't work that way. It reminds me of autodecoding where hasLength!string is false, yet string.length works. I think the idup rewrite is still viable, just not as seamless (e.g. if the tuple has a type, then auto x = i"..." will not be a string as outlined in the DIP). Adam is trying to set up an implementation to play with, where we can see if it's viable. -Steve
Jan 31 2021
parent reply Dukc <ajieskola gmail.com> writes:
On Sunday, 31 January 2021 at 15:54:12 UTC, Steven Schveighoffer 
wrote:
 I think we're going to end up with something like this. The 
 more I think about it, the more I feel that having a primary 
 type be the tuple is required.

 [snip]
Good. Remember though - you still have to do something to the `mixin(i"...")` case Backus warned about. My suggestion about the user manually selecting the type is one option, but not the only one. Perhaps mixins can be special case to invoke `idup` if any of their arguments are instanced from `interp`.
Feb 01 2021
next sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Monday, 1 February 2021 at 16:00:54 UTC, Dukc wrote:
 Perhaps mixins can be special case to invoke `idup` if any of 
 their arguments are instanced from `interp`.
Yeah, the grammar could always just special case overload mixin(Interpolated). Or .stringof could be redefined so like interp!"foo".stringof == "foo" or whatever. .... or just ban it. Make mixin(i"...") a compile error so the user can do their own thing. Frankly I think it is madness that mixin uses stringof in the first place since it is so poorly defined. The spec specifically says: https://dlang.org/spec/property.html#stringof Implementation Defined: The string representation for a type or expression can vary. Best Practices: Do not use .stringof for code generation. Instead use the identifier trait, or one of the Phobos helper functions such as std.traits.fullyQualifiedName. So why the heck is that built into the language?!?
Feb 01 2021
next sibling parent reply Dukc <ajieskola gmail.com> writes:
On Monday, 1 February 2021 at 16:10:46 UTC, Adam D. Ruppe wrote:
 On Monday, 1 February 2021 at 16:00:54 UTC, Dukc wrote:
 Perhaps mixins can be special case to invoke `idup` if any of 
 their arguments are instanced from `interp`.
Yeah, the grammar could always just special case overload mixin(Interpolated). Or .stringof could be redefined so like interp!"foo".stringof == "foo" or whatever.
One problem: `.stringof` is supposed to be a source representation. "foo" in source represents a string, not part of an interpolated string.
 .... or just ban it. Make mixin(i"...") a compile error so the 
 user can do their own thing.
I advise against that. Quoting https://dlang.org/blog/2019/10/15/my-vision-of-ds-future/:
 String interpolation
 
 I was initially against this, but the more I think about it the 
 more it seems to make
 sense for D. Why? String mixins. Code generation is one of D’s 
 greatest strengths, and
 token strings enable visually pleasing blocks of code that are 
 actually “just strings”.
 String interpolation would make them vastly easier to use. As 
 it happens, there’s a draft > DIP for it in the pipeline.
You don't want to require the user add an extra symbol for every interpolated string in it's most important use case.
Feb 01 2021
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Monday, 1 February 2021 at 16:22:02 UTC, Dukc wrote:
 One problem: `.stringof` is supposed to be a source 
 representation. "foo" in source represents a string, not part 
 of an interpolated string.
Oh I just thought about it some more and interp isn't the problem. Automatic stringof is madness to begin with since this problem isn't unique to this feature. mixin(genric_type.stringof) is *always* wrong due to the mismatched scope so there's no good reason to have such a broken feature built into the language. Consider this too: mixin(i"hi ${a+b}") - without the special rule - would expand to mixin("hi ", "a+b") so even that a+b is interpreted in a different scope. So we *have* to special case this somehow to work around this broken mixin string "feature". Ugh.
Feb 01 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 1 February 2021 at 16:40:54 UTC, Adam D. Ruppe wrote:
 Consider this too: mixin(i"hi ${a+b}") - without the special 
 rule - would expand to mixin("hi ", "a+b") so even that a+b is 
 interpreted in a different scope.
No, you've added an extra level of quoting by mistake. It would expand to `mixin("hi ", a+b)`. The expression `a+b` would be evaluated using CTFE, and then .stringof (i.e., the .toString method) would be called on the result. Here's the relevant frontend code: https://github.com/dlang/dmd/blob/v2.095.0/src/dmd/expressionsem.d#L91
Feb 01 2021
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Monday, 1 February 2021 at 17:06:36 UTC, Paul Backus wrote:
 No, you've added an extra level of quoting by mistake.
oh yeah, that's right. So we could prolly just define the stringof of interp to be slightly less crazy (the source version either being the proper fqn or the literal itself) and then there's no special case in mixin.
Feb 01 2021
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 1 February 2021 at 17:52:35 UTC, Adam D. Ruppe wrote:
 On Monday, 1 February 2021 at 17:06:36 UTC, Paul Backus wrote:
 No, you've added an extra level of quoting by mistake.
oh yeah, that's right. So we could prolly just define the stringof of interp to be slightly less crazy (the source version either being the proper fqn or the literal itself) and then there's no special case in mixin.
Well, you can't define .stringof in user code--at least, not in a way the compiler will recognize--so you'd have to make the interp literals into a new builtin type rather than a library template.
Feb 01 2021
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 2/1/21 1:15 PM, Paul Backus wrote:
 On Monday, 1 February 2021 at 17:52:35 UTC, Adam D. Ruppe wrote:
 On Monday, 1 February 2021 at 17:06:36 UTC, Paul Backus wrote:
 No, you've added an extra level of quoting by mistake.
oh yeah, that's right. So we could prolly just define the stringof of interp to be slightly less crazy (the source version either being the proper fqn or the literal itself) and then there's no special case in mixin.
Well, you can't define .stringof in user code--at least, not in a way the compiler will recognize--so you'd have to make the interp literals into a new builtin type rather than a library template.
I kind of don't want: mixin(i"int ${foo} = ${bar};"); to do something different than mixin(i"int ${foo} = ${bar};".idup); Because that would be utterly confusing. If stringof isn't going to produce the same output, the idup should be inserted. idup should be CTFE able at all times (the one exception is floats, but that is a separate issue that needs solving regardless). -Steve
Feb 01 2021
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/1/21 12:52 PM, Adam D. Ruppe wrote:
 On Monday, 1 February 2021 at 17:06:36 UTC, Paul Backus wrote:
 No, you've added an extra level of quoting by mistake.
oh yeah, that's right. So we could prolly just define the stringof of interp to be slightly less crazy (the source version either being the proper fqn or the literal itself) and then there's no special case in mixin.
Thought: std.bitmanip.bitfields has some gnarly code that generates shifting and masking code for bitfields. It would be great to show how that code is vastly improved by the DIP.
Feb 02 2021
next sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Tuesday, 2 February 2021 at 13:06:06 UTC, Andrei Alexandrescu 
wrote:
 Thought: std.bitmanip.bitfields has some gnarly code that 
 generates shifting and masking code for bitfields. It would be 
 great to show how that code is vastly improved by the DIP.
Indeed, yikes. And all that CTFE ~ work... ctfe performs very poorly with ~. So it would be worth making the interpolation function (idup or whatever we call it) be sure to avoid that. Doing array copies into preallocated buffers (buffer[spot .. spot + what.length] = what[], spot += what.length) is *significantly* faster, you can double or triple the speed of builds and cut dmd memory use down by 10x with those simple changes if you use the concated mixins heavily (my little jni.d did it like this at first and I did realize such enormous gains there, it was incredible). That's fairly easy to do with the dip too since 1) the string literal pieces themselves are all known at compile time and 2) the length and types of the argument tuple is also known ahead of time, so you can use a static array to cache their intermediates (as necessary) while gathering their runtime lengths for the final copy. If I can find some time later today I'll see what I can do with it.
Feb 02 2021
prev sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 2/2/21 8:06 AM, Andrei Alexandrescu wrote:
 On 2/1/21 12:52 PM, Adam D. Ruppe wrote:
 On Monday, 1 February 2021 at 17:06:36 UTC, Paul Backus wrote:
 No, you've added an extra level of quoting by mistake.
oh yeah, that's right. So we could prolly just define the stringof of interp to be slightly less crazy (the source version either being the proper fqn or the literal itself) and then there's no special case in mixin.
Thought: std.bitmanip.bitfields has some gnarly code that generates shifting and masking code for bitfields. It would be great to show how that code is vastly improved by the DIP.
Yes, that is a great idea for a use case. Thanks for the pointer! -Steve
Feb 02 2021
prev sibling parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
On Monday, 1 February 2021 at 16:10:46 UTC, Adam D. Ruppe wrote:
 [complaining about .stringof]

 So why the heck is that built into the language?!?
It makes for nice error messages, tho. I don't care much about the exact string there, only that it is recognizable (which for (++i).stringof it clearly isn't).
Feb 03 2021
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Wednesday, 3 February 2021 at 21:13:44 UTC, Q. Schroll wrote:
 It makes for nice error messages, tho. I don't care much about 
 the exact string there, only that it is recognizable (which for 
 (++i).stringof it clearly isn't).
stringof for error messages is fine. What perplexes me is that mixin(x) will translate to mixin(x.stringof) automatically even though that's a mistake more often than not.
Feb 03 2021
parent reply Daniel N <no public.email> writes:
On Wednesday, 3 February 2021 at 20:12:24 UTC, Adam D. Ruppe 
wrote:
 On Wednesday, 3 February 2021 at 20:02:57 UTC, Daniel N wrote:
 Wouldn't this lowering be both simpler and more more efficient?
Your change wouldn't expose the string for compile time processing. That's the real benefit of interp!"string" - it is available for CTFE rewriting by the function.
Guess it's not allowed to keep discussing in the feedback thread so I have to copy it here. alias I(T...) = T; alias X(T...) = I!(interp!(T[0]), interp!(T[1])); Sorry for being daft, I simply don't get it... there's no issue with applying map to a tuple of string literals... X!("one","two") Maybe you have to jump through some extra hoops, but in the common case, is more optimized if there are no superfluous template instances for every little string... /Daniel
Feb 03 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Wednesday, 3 February 2021 at 22:07:47 UTC, Daniel N wrote:
 On Wednesday, 3 February 2021 at 20:12:24 UTC, Adam D. Ruppe 
 wrote:
 On Wednesday, 3 February 2021 at 20:02:57 UTC, Daniel N wrote:
 Wouldn't this lowering be both simpler and more more 
 efficient?
Your change wouldn't expose the string for compile' time processing. That's the real benefit of interp!"string" - it is available for CTFE rewriting by the function.
Guess it's not allowed to keep discussing in the feedback thread so I have to copy it here. alias I(T...) = T; alias X(T...) = I!(interp!(T[0]), interp!(T[1])); Sorry for being daft, I simply don't get it... there's no issue with applying map to a tuple of string literals... X!("one","two")
The idea is that, even when `interp!"whatever"` is passed as a *runtime* argument to a template function, that function can access the string "whatever" at compile time, because it's part of the argument's *type* rather than its *value*. void fun(Args...)(Args args) { static foreach (arg; args) { static if (is(arg == interp!s, string s)) { // Allowed to do this because `s` is known at compile time pragma(msg, s); } } } // prints "Hello " at compile-time fun(i"Hello ${name}");
Feb 03 2021
parent reply Daniel N <no public.email> writes:
On Wednesday, 3 February 2021 at 22:15:45 UTC, Paul Backus wrote:
 On Wednesday, 3 February 2021 at 22:07:47 UTC, Daniel N wrote:
 On Wednesday, 3 February 2021 at 20:12:24 UTC, Adam D. Ruppe 
 wrote:
 On Wednesday, 3 February 2021 at 20:02:57 UTC, Daniel N wrote:
 Wouldn't this lowering be both simpler and more more 
 efficient?
The idea is that, even when `interp!"whatever"` is passed as a *runtime* argument to a template function, that function can access the string "whatever" at compile time, because it's part of the argument's *type* rather than its *value*. void fun(Args...)(Args args) { static foreach (arg; args) { static if (is(arg == interp!s, string s)) { // Allowed to do this because `s` is known at compile time pragma(msg, s); } } } // prints "Hello " at compile-time fun(i"Hello ${name}");
Thank you! It is very impressive, brilliant and clever, yet I don't think it's the right way. With a plain tuple it is possible to create a wrapper which generates DIP1036 style lowering (or simply work directly in the template with the value params). template fun(T...) { void fun() { fun(interp!(T[0])(), interp!(T[1])()); } } In order of greatness: 1) Opt-In Bloat 2) Out-Out Bloat 3) Unavoidable Bloat <- Current DIP1036 Yes, as a library author you are forced to use value templates... but it still works, no real expressive power is lost. Shouldn't we optimize for the common-case? (writeln and mixin) Strings are ubiquitous, lowering to a plain tuple is the least possible bloat and also most powerful (support for ref etc), but with my proposed simplification you are now free to opt-in to more advanced meta programming _when you need it_, pay as you go!
Feb 03 2021
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 4 February 2021 at 07:09:07 UTC, Daniel N wrote:
 With a plain tuple it is possible to create a wrapper which 
 generates DIP1036 style lowering (or simply work directly in 
 the template with the value params).
That forces CTFE evaluation of *everything* which is pretty useless. int bar; fun!(i"foo {$bar+5}"); // bar cannot be evaluated at compile time Only the literals themselves should be in the compile time tuple, with the rest of them staying right where they are (which can be either side). It is impossible to express that in a library, since to call a library function/instantiate a library template, you must choose one or the other! (The exception being if the lib function works only in terms of strings, and you use mixin at the call site.) That's really the reason why this is proposed a language feature in the first place. A library *can't* do it well.
 3) Unavoidable Bloat <- Current DIP1036
There's a lot of FUD around the community about templates lately and while there's a kernel of truth to it, it is massively overblown. The only part of the template that is kept at runtime at all is the toString method, and it is trivial - return string literal. At the usage point, it is inlined, so I guess it only keeps the function body in case someone takes an address of it. A higher quality implementation (perhaps an optimized build already does, i've only tested my poc on dmd w/o flags) could skip this too and the template itself is invisible in the binary. Inside the compiler, this template instance does add a couple entries to the symbol table, but they tend to be very small (proportional to the size of the string literal fragment in the original code). Where huge symbols become problematic is when they are recursive or otherwise nested since that's non-linear generated growth. It is rarely, if ever, the result of linear growth in the actual written codebase. (Of course, you could write some ctfe function that mixes in a huge interpolated string that mixes in another huge interpolated string but you can deliberately sabotage ctfe in all kinds of other ways too.) Now, there is something to be said about functions receiving it, since i"foo" and i"bar" are different types, so you'd have two expansions. Of course, that's also the case with anything else you spread too... send X and const X and you get to separate instances. D in general could use a "collapse these instances" feature. But even with the status quo, if you just forward converted arguments to another function to do the bulk of the work, this can also be pretty easily optimized by existing compilers. void writeln(T...)(T t) { foreach(arg; t) write(arg.toString); write("\n"); }
 Strings are ubiquitous, lowering to a plain tuple is the least 
 possible bloat and also most powerful (support for ref etc), 
 but with my proposed simplification you are now free to opt-in 
 to more advanced meta programming _when you need it_, pay as 
 you go!
Without the string in the compile time tuple from the language, you lost most the potential power.
Feb 04 2021
parent Daniel N <no public.email> writes:
On Thursday, 4 February 2021 at 13:44:48 UTC, Adam D. Ruppe wrote:
 On Thursday, 4 February 2021 at 07:09:07 UTC, Daniel N wrote:
 With a plain tuple it is possible to create a wrapper which 
 generates DIP1036 style lowering (or simply work directly in 
 the template with the value params).
That forces CTFE evaluation of *everything* which is pretty useless. int bar; fun!(i"foo {$bar+5}"); // bar cannot be evaluated at compile time
Expressions could simply be lowered to temporaries on demand... fun!(i"foo ${bar+5}"); ====================== (int __expr0) { return fun!("foo ", __expr0); }(bar+5); Which works perfectly fine, it does archive my goal of reducing the complexity in the *common case*. Arguably it's even more complex in the case where the lambda is needed, so I can understand if not everyone is happy with my idea... but if it was my proposal I wouldn't even support expressions, plain "$bar" is the sweet spot, no need for ${}, nor temporaries, keep it simple. Well, maybe you are right about template instance bloat being exaggerated, I didn't benchmark it recently. /Daniel N
Feb 04 2021
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 2/1/21 11:00 AM, Dukc wrote:
 On Sunday, 31 January 2021 at 15:54:12 UTC, Steven Schveighoffer wrote:
 I think we're going to end up with something like this. The more I 
 think about it, the more I feel that having a primary type be the 
 tuple is required.

 [snip]
Good. Remember though - you still have to do something to the `mixin(i"...")` case Backus warned about. My suggestion about the user manually selecting the type is one option, but not the only one. Perhaps mixins can be special case to invoke `idup` if any of their arguments are instanced from `interp`.
I think we have to special case mixin and assert, which is reasonable, since this is a language feature. -Steve
Feb 01 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 1 February 2021 at 16:16:11 UTC, Steven Schveighoffer 
wrote:
 On 2/1/21 11:00 AM, Dukc wrote:
 Good. Remember though - you still have to do something to the 
 `mixin(i"...")` case Backus warned about. My suggestion about 
 the user manually selecting the type is one option, but not 
 the only one. Perhaps mixins can be special case to invoke 
 `idup` if any of their arguments are instanced from `interp`.
 
I think we have to special case mixin and assert, which is reasonable, since this is a language feature.
Please, no. This way leads to the Dark Side.
Feb 01 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 2/1/21 11:37 AM, Paul Backus wrote:
 On Monday, 1 February 2021 at 16:16:11 UTC, Steven Schveighoffer wrote:
 On 2/1/21 11:00 AM, Dukc wrote:
 Good. Remember though - you still have to do something to the 
 `mixin(i"...")` case Backus warned about. My suggestion about the 
 user manually selecting the type is one option, but not the only one. 
 Perhaps mixins can be special case to invoke `idup` if any of their 
 arguments are instanced from `interp`.
I think we have to special case mixin and assert, which is reasonable, since this is a language feature.
Please, no. This way leads to the Dark Side.
There is literally no use case for calling mixin with the expanded tuple. If we are doing rewrites, here is one place we should always do it. Besides, we are looking for consistency, and something.stringof is not consistent as pointed out already. The idup call will be consistent. -Steve
Feb 01 2021
parent reply rikki cattermole <rikki cattermole.co.nz> writes:
On 02/02/2021 5:41 AM, Steven Schveighoffer wrote:
 There is literally no use case for calling mixin with the expanded 
 tuple. If we are doing rewrites, here is one place we should always do it.
enum Prop = ...; enum PropValue = ...; mixin(i"obj.${Prop} = PropValue;"); Are you referring to that? Because that could be useful.
Feb 01 2021
next sibling parent Paul Backus <snarwin gmail.com> writes:
On Monday, 1 February 2021 at 17:19:09 UTC, rikki cattermole 
wrote:
 On 02/02/2021 5:41 AM, Steven Schveighoffer wrote:
 There is literally no use case for calling mixin with the 
 expanded tuple. If we are doing rewrites, here is one place we 
 should always do it.
enum Prop = ...; enum PropValue = ...; mixin(i"obj.${Prop} = PropValue;"); Are you referring to that? Because that could be useful.
It doesn't work the way you think it does. https://forum.dlang.org/post/bvdfzfbqybarvpgydewx forum.dlang.org
Feb 01 2021
prev sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 2/1/21 12:19 PM, rikki cattermole wrote:
 On 02/02/2021 5:41 AM, Steven Schveighoffer wrote:
 There is literally no use case for calling mixin with the expanded 
 tuple. If we are doing rewrites, here is one place we should always do 
 it.
enum Prop = ...; enum PropValue = ...; mixin(i"obj.${Prop} = PropValue;"); Are you referring to that? Because that could be useful.
I'm saying let's make what you wrote work, instead of being a compiler error. Easiest way to accomplish this is to convert into a string. -Steve
Feb 01 2021
prev sibling parent reply Dukc <ajieskola gmail.com> writes:
On Friday, 29 January 2021 at 19:10:55 UTC, Steven Schveighoffer 
wrote:
 On 1/29/21 7:58 AM, Dukc wrote:
 I was mainly thinking that I'd have easier time 
 differentiating between an `int` in interpolated string and 
 `int` passed before/after the interpolated string.
We debated whether one should be able to figure out the original interpolation string from the parameters. I don't think it's necessary, and adds unnecessary complexity. Just passing the expression data directly makes things easy to deal with instead of adding an extra type to deal with. If you can come up with a reasonable use case for differentiating, we can discuss.
Okay, finally did this: ``` void writeAtPositions(T...)(char[] wArea, T args) { import std; char[] outp = wArea; foreach(arg; args) static if(is(typeof(arg) : size_t) && !is(typeof(arg) : char)) { outp = wArea[arg .. $]; } else { auto argStr= arg.text; outp[0 .. argStr.length] = argStr[]; outp = outp.drop(argStr.length); } } void main() { import std.stdio; char[] text = "x=000,y=000,z=000".dup; text.writeAtPositions(2, '2', "55", 16, "6"); text.writeln; } ``` Consider passing ints in interpolated strings to `writeAtPositions`. Only change needed to work with my suggestion would be adding `!__traits(isSame, TemplateOf(typeof(arg), interp))` to the `static if`, (I assume interp!int would have `alias value this`). If the interpolated string `int`s are passed like any `int`s, one would probably have to manually cast the `int`s among interpolated strings to strings, to work with this function.
Feb 03 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 2/3/21 10:54 AM, Dukc wrote:
 On Friday, 29 January 2021 at 19:10:55 UTC, Steven Schveighoffer wrote:
 On 1/29/21 7:58 AM, Dukc wrote:
 I was mainly thinking that I'd have easier time differentiating 
 between an `int` in interpolated string and `int` passed before/after 
 the interpolated string.
We debated whether one should be able to figure out the original interpolation string from the parameters. I don't think it's necessary, and adds unnecessary complexity. Just passing the expression data directly makes things easy to deal with instead of adding an extra type to deal with. If you can come up with a reasonable use case for differentiating, we can discuss.
Okay, finally did this: ``` void writeAtPositions(T...)(char[] wArea, T args) {  import std;    char[] outp = wArea;    foreach(arg; args)    static if(is(typeof(arg) : size_t) && !is(typeof(arg) : char))    {  outp = wArea[arg .. $];    }  else    {  auto argStr= arg.text;       outp[0 .. argStr.length] = argStr[];       outp = outp.drop(argStr.length);    } } void main() {  import std.stdio;    char[] text = "x=000,y=000,z=000".dup;    text.writeAtPositions(2, '2', "55", 16, "6");    text.writeln; } ``` Consider passing ints in interpolated strings to `writeAtPositions`. Only change needed to work with my suggestion would be adding `!__traits(isSame, TemplateOf(typeof(arg), interp))` to the `static if`, (I assume interp!int would have `alias value this`). If the interpolated string `int`s are passed like any `int`s, one would probably have to manually cast the `int`s among interpolated strings to strings, to work with this function.
So the idea here is to use string interpolations to represent the string parts of the parameters. To reimagine what you are thinking, something like: int val = 255; int otherval = 6; text.writeAtPositions(2, i"${val}", 16, i"${otherval}"); So the parameters are going to be: text, 2, interp!""(), val, interp!""(), 16, interp!""(), otherval, interp!""()) First, I'd suggest instrumenting the positions instead of the data, since that's the exceptional case: struct Pos { size_t p; } auto pos(size_t x) { return Pos(x); } text.writeAtPositions(2.pos, i${val}", 16.pos, i"${otherval}"); And then you have a general mechanism that doesn't even need interpolation strings: text.writeAtPositions(2.pos, val, 16.pos, otherval) And now, you can use interpolation strings as the "things to write" without ambiguity. You can even use a single interpolation string (with the positions being interpolation parameters). I would say the non-interpolation form is better for when you are just passing in parameters, and the interpolation form is great when you want, well, interpolations. I get the thrust of what you are saying. But technically, if you are having to reengineer the function to deal with interpolation strings, you should rethink how the API works. FYI, something I have considered recently, because I don't *love* the mechanism for overloading is to put a non-template parameter first and last in the sequence. Which then makes it possible to discover where the sequences begin and end. So instead of having to use template constraints for overloading: void foo(T...)(T args) if (is(T[0] : interp!S, S)) you just use standard overloading: void foo(T...)(__iStart, T args) And as a bonus, you can discern interpolation parameters from regular ones (if desired). -Steve
Feb 03 2021
parent Dukc <ajieskola gmail.com> writes:
On Wednesday, 3 February 2021 at 16:38:15 UTC, Steven 
Schveighoffer wrote:
 First, I'd suggest instrumenting the positions instead of the 
 data, since that's the exceptional case:

 [snip]

 And now, you can use interpolation strings as the "things to 
 write" without ambiguity. You can even use a single 
 interpolation string (with the positions being interpolation 
 parameters). I would say the non-interpolation form is better 
 for when you are just passing in parameters, and the 
 interpolation form is great when you want, well, interpolations.

 I get the thrust of what you are saying. But technically, if 
 you are having to reengineer the function to deal with 
 interpolation strings, you should rethink how the API works.
Good, obviously you got the point and your answer sounds convincing enough that I don't think there's need for me to argue about that one any further.
Feb 03 2021
prev sibling parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
On Friday, 29 January 2021 at 12:58:32 UTC, Dukc wrote:
 On Thursday, 28 January 2021 at 14:58:36 UTC, Steven 
 Schveighoffer at the feedback theard wrote:
 On 1/28/21 3:35 AM, Dukc wrote:
 The DIP states that foo(i"a:${a}, ${b}.") is rewritten as 
 `foo(Interp!"a:", a, Interp!", ", b, Interp!".")`. It think 
 it's better to rewrite it as `foo(Interp!"a:", 
 Interp!typeof(a)(a), Interp!", ", Interp!typeof(b)(b), 
 Interp!".")`. That way, `foo` has easier time introspecting 
 which came from the interpolated string.
First, I don't think it's critical for overloading, and will simply add to the template bloat. What are you going to do differently with `a` than you would with `Interp!(typeof(a))(a)`?
I was mainly thinking that I'd have easier time differentiating between an `int` in interpolated string and `int` passed before/after the interpolated string. And I have a type that will implicitly convert to string if I want to do that - no need to call `to!string(a)` or `a.Interp!(typeof(a))` first.
 The parameters are guaranteed to start and end with an 
 InterpolationLiteral, so one can assume that non-literal 
 arguments are interspersed inside the literal.
It can be done, but it sounds more complex for the introspecting function. I'm not strict about this though, what the DIP now proposes be worth it to be able to pass `ref` parameters in interpolated strings.
 The type of interpolated string literal is very special 
 cased. [snip]
I was fully aware that this would be the most controversial part. I feel like it will not be full of corner cases, but I'm not sure. Can you specify any? Consider a normal string literal can be used as a string, immutable(char)*, wstring, or dstring. I find it very similar to this feature, and I don't feel like there are a lot of corner cases there.
A string literal is a string that is implicitly assignable to the other alternatives via value range propagation mechanics, or that's how I understand it at least. The compromise that the interpolated string would be an expanded tuple, that would be implicitly assignable to string via value range propagation mechanics, sounds acceptable. But it needs to be clear IMO what the primary type of an interpolated string is. If it is not an expanded tuple, what it is then? I mean that this must be guaranteed to pass IMO:
Sorry, I'm late to the game here. This reminds me of slices vs static arrays. As a reminder, to a newcomer, auto xs = [ 1, 2, 3 ]; looks like it would infer int[3] as the type of xs. It is obviously the most descriptive type for the literal. Why would it infer int[] forgetting its compile-time known length and even do an allocation? That seems so much worse. Even typeof([1,2,3]) is int[] and not int[3]. We know why D does it the way it does and goes int[3] with no allocation only if requested explicitly. You can do that with a template with a flexible length like this: void takesStaticArray(size_t n)(int[n] staticArray); Here, `n` can usually be inferred from the argument. Interpolated strings could do the exact same thing: 1. make typeof(i"...") result to `string`. 2. make auto str = i"..." infer string (cf. typeof) and gc-allocate if necessary. 3. give i"..." a secondary type akin to [1,2,3] having int[3] as a secondary type. If an interpolated string is bound to a parameter of that secondary type (`interp` in the DIP) it uses its secondary type (cf. calling takesStaticArray with [1,2,3]). In any other case, e.g. `auto` or otherwise generic template parameters will infer string. Getting a string is probably what most users expect most of the time. Handling the secondary type must be explicit. It is almost an implementation detail that shouldn't be exposed to the user too easily. (I'll post that to the feedback thread as well.)
Feb 03 2021
next sibling parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Wednesday, 3 February 2021 at 16:52:23 UTC, Q. Schroll wrote:
 On Friday, 29 January 2021 at 12:58:32 UTC, Dukc wrote:
 [...]
Sorry, I'm late to the game here. This reminds me of slices vs static arrays. As a reminder, to a newcomer, auto xs = [ 1, 2, 3 ]; looks like it would infer int[3] as the type of xs. It is obviously the most descriptive type for the literal. Why would it infer int[] forgetting its compile-time known length and even do an allocation? That seems so much worse. Even typeof([1,2,3]) is int[] and not int[3]. We know why D does it the way it does and goes int[3] with no allocation only if requested explicitly. You can do that with a template with a flexible length like this: void takesStaticArray(size_t n)(int[n] staticArray); Here, `n` can usually be inferred from the argument. [...]
I also think that's reasonable, but Adam didn't seem to agree iirc (default to string)
Feb 03 2021
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 2/3/21 11:52 AM, Q. Schroll wrote:
 On Friday, 29 January 2021 at 12:58:32 UTC, Dukc wrote:
 On Thursday, 28 January 2021 at 14:58:36 UTC, Steven Schveighoffer at 
 the feedback theard wrote:
 On 1/28/21 3:35 AM, Dukc wrote:
 The DIP states that foo(i"a:${a}, ${b}.") is rewritten as 
 `foo(Interp!"a:", a, Interp!", ", b, Interp!".")`. It think it's 
 better to rewrite it as `foo(Interp!"a:", Interp!typeof(a)(a), 
 Interp!", ", Interp!typeof(b)(b), Interp!".")`. That way, `foo` has 
 easier time introspecting which came from the interpolated string.
First, I don't think it's critical for overloading, and will simply add to the template bloat. What are you going to do differently with `a` than you would with `Interp!(typeof(a))(a)`?
I was mainly thinking that I'd have easier time differentiating between an `int` in interpolated string and `int` passed before/after the interpolated string. And I have a type that will implicitly convert to string if I want to do that - no need to call `to!string(a)` or `a.Interp!(typeof(a))` first.
 The parameters are guaranteed to start and end with an 
 InterpolationLiteral, so one can assume that non-literal arguments 
 are interspersed inside the literal.
It can be done, but it sounds more complex for the introspecting function. I'm not strict about this though, what the DIP now proposes be worth it to be able to pass `ref` parameters in interpolated strings.
 The type of interpolated string literal is very special cased. [snip]
I was fully aware that this would be the most controversial part. I feel like it will not be full of corner cases, but I'm not sure. Can you specify any? Consider a normal string literal can be used as a string, immutable(char)*, wstring, or dstring. I find it very similar to this feature, and I don't feel like there are a lot of corner cases there.
A string literal is a string that is implicitly assignable to the other alternatives via value range propagation mechanics, or that's how I understand it at least. The compromise that the interpolated string would be an expanded tuple, that would be implicitly assignable to string via value range propagation mechanics, sounds acceptable. But it needs to be clear IMO what the primary type of an interpolated string is. If it is not an expanded tuple, what it is then? I mean that this must be guaranteed to pass IMO:
Sorry, I'm late to the game here. This reminds me of slices vs static arrays. As a reminder, to a newcomer,     auto xs = [ 1, 2, 3 ]; looks like it would infer int[3] as the type of xs. It is obviously the most descriptive type for the literal. Why would it infer int[] forgetting its compile-time known length and even do an allocation? That seems so much worse. Even typeof([1,2,3]) is int[] and not int[3]. We know why D does it the way it does and goes int[3] with no allocation only if requested explicitly. You can do that with a template with a flexible length like this:     void takesStaticArray(size_t n)(int[n] staticArray); Here, `n` can usually be inferred from the argument. Interpolated strings could do the exact same thing: 1. make typeof(i"...") result to `string`. 2. make auto str = i"..." infer string (cf. typeof) and gc-allocate if necessary. 3. give i"..." a secondary type akin to [1,2,3] having int[3] as a secondary type.
I don't want to do it that way, because then the overload is not easy to ask for. I really really don't want the string form to be passed into a vararg template. I've been having second thoughts about having auto x = i"..."; be a string, and typeof(i"...") be a string. See my later response here: https://forum.dlang.org/post/rv6jr4$uor$1 digitalmars.com
 If an interpolated string is bound to a parameter of that secondary type 
 (`interp` in the DIP) it uses its secondary type (cf. calling 
 takesStaticArray with [1,2,3]). In any other case, e.g. `auto` or 
 otherwise generic template parameters will infer string.
 
 Getting a string is probably what most users expect most of the time. 
 Handling the secondary type must be explicit. It is almost an 
 implementation detail that shouldn't be exposed to the user too easily.
I'm not sure how this could be possible. You can't say T... and have it match the tuple over the string, unless the primary type is the tuple. But if you have ideas, surely I'd prefer it to be a string first in most cases! -Steve
Feb 03 2021
parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
On Wednesday, 3 February 2021 at 17:40:40 UTC, Steven 
Schveighoffer wrote:
 On 2/3/21 11:52 AM, Q. Schroll wrote:
 On Friday, 29 January 2021 at 12:58:32 UTC, Dukc wrote:
 On Thursday, 28 January 2021 at 14:58:36 UTC, Steven 
 Schveighoffer at the feedback theard wrote:
 On 1/28/21 3:35 AM, Dukc wrote:
 The DIP states that foo(i"a:${a}, ${b}.") is rewritten as 
 `foo(Interp!"a:", a, Interp!", ", b, Interp!".")`. It think 
 it's better to rewrite it as `foo(Interp!"a:", 
 Interp!typeof(a)(a), Interp!", ", Interp!typeof(b)(b), 
 Interp!".")`. That way, `foo` has easier time introspecting 
 which came from the interpolated string.
First, I don't think it's critical for overloading, and will simply add to the template bloat. What are you going to do differently with `a` than you would with `Interp!(typeof(a))(a)`?
I was mainly thinking that I'd have easier time differentiating between an `int` in interpolated string and `int` passed before/after the interpolated string. And I have a type that will implicitly convert to string if I want to do that - no need to call `to!string(a)` or `a.Interp!(typeof(a))` first.
 The parameters are guaranteed to start and end with an 
 InterpolationLiteral, so one can assume that non-literal 
 arguments are interspersed inside the literal.
It can be done, but it sounds more complex for the introspecting function. I'm not strict about this though, what the DIP now proposes be worth it to be able to pass `ref` parameters in interpolated strings.
 The type of interpolated string literal is very special 
 cased. [snip]
I was fully aware that this would be the most controversial part. I feel like it will not be full of corner cases, but I'm not sure. Can you specify any? Consider a normal string literal can be used as a string, immutable(char)*, wstring, or dstring. I find it very similar to this feature, and I don't feel like there are a lot of corner cases there.
A string literal is a string that is implicitly assignable to the other alternatives via value range propagation mechanics, or that's how I understand it at least. The compromise that the interpolated string would be an expanded tuple, that would be implicitly assignable to string via value range propagation mechanics, sounds acceptable. But it needs to be clear IMO what the primary type of an interpolated string is. If it is not an expanded tuple, what it is then? I mean that this must be guaranteed to pass IMO:
Sorry, I'm late to the game here. This reminds me of slices vs static arrays. As a reminder, to a newcomer,     auto xs = [ 1, 2, 3 ]; looks like it would infer int[3] as the type of xs. It is obviously the most descriptive type for the literal. Why would it infer int[] forgetting its compile-time known length and even do an allocation? That seems so much worse. Even typeof([1,2,3]) is int[] and not int[3]. We know why D does it the way it does and goes int[3] with no allocation only if requested explicitly. You can do that with a template with a flexible length like this:     void takesStaticArray(size_t n)(int[n] staticArray); Here, `n` can usually be inferred from the argument. Interpolated strings could do the exact same thing: 1. make typeof(i"...") result to `string`. 2. make auto str = i"..." infer string (cf. typeof) and gc-allocate if necessary. 3. give i"..." a secondary type akin to [1,2,3] having int[3] as a secondary type.
I don't want to do it that way, because then the overload is not easy to ask for. I really really don't want the string form to be passed into a vararg template.
I think I understand you. I want the best of all worlds, too. Maybe I'm just not seeing it. As of now, I'm really convinced that, while it would be nice to have, it just would break too much intuition. Using something as simple and common as i"..." correctly MUST be trivial. One shouldn't have to look up how it works.
 I've been having second thoughts about having auto x = i"..."; 
 be a string, and typeof(i"...") be a string. See my later 
 response here: 
 https://forum.dlang.org/post/rv6jr4$uor$1 digitalmars.com
I didn't think too much about typeof(i"..."), but I agree that it being string but behaving differently form a plain string may be odd.
 If an interpolated string is bound to a parameter of that 
 secondary type (`interp` in the DIP) it uses its secondary 
 type (cf. calling takesStaticArray with [1,2,3]). In any other 
 case, e.g. `auto` or otherwise generic template parameters 
 will infer string.
 
 Getting a string is probably what most users expect most of 
 the time. Handling the secondary type must be explicit. It is 
 almost an implementation detail that shouldn't be exposed to 
 the user too easily.
I'm not sure how this could be possible. You can't say T... and have it match the tuple over the string, unless the primary type is the tuple.
I'm not completely sure what that paragraph means.
 But if you have ideas, surely I'd prefer it to be a string 
 first in most cases!
I'd say the best idea is to make i"..." decay into string unless forced to be interp!".." (or really being a better match, really think about it like binding [1,2,3] to int[3]). An easy way out would be giving the actual type of i"..." a property .interp or .__interp that (cf. tuple's expand) returns the interp sequence. That way, it can be tested in pragma(msg, i"...".__interp) while also being correct whenever used in a canonical form. __interp couldn't really be a library function. An alternative would be __traits(interp, i"..."). The interpolation tuple really is an implementation detail that is relevant for an important, but still a rather small minority of functions.
Feb 03 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 2/3/21 4:00 PM, Q. Schroll wrote:
 On Wednesday, 3 February 2021 at 17:40:40 UTC, Steven Schveighoffer wrote:
 On 2/3/21 11:52 AM, Q. Schroll wrote:
 On Friday, 29 January 2021 at 12:58:32 UTC, Dukc wrote:
 On Thursday, 28 January 2021 at 14:58:36 UTC, Steven Schveighoffer 
 at the feedback theard wrote:
 On 1/28/21 3:35 AM, Dukc wrote:
 The DIP states that foo(i"a:${a}, ${b}.") is rewritten as 
 `foo(Interp!"a:", a, Interp!", ", b, Interp!".")`. It think it's 
 better to rewrite it as `foo(Interp!"a:", Interp!typeof(a)(a), 
 Interp!", ", Interp!typeof(b)(b), Interp!".")`. That way, `foo` 
 has easier time introspecting which came from the interpolated 
 string.
First, I don't think it's critical for overloading, and will simply add to the template bloat. What are you going to do differently with `a` than you would with `Interp!(typeof(a))(a)`?
I was mainly thinking that I'd have easier time differentiating between an `int` in interpolated string and `int` passed before/after the interpolated string. And I have a type that will implicitly convert to string if I want to do that - no need to call `to!string(a)` or `a.Interp!(typeof(a))` first.
 The parameters are guaranteed to start and end with an 
 InterpolationLiteral, so one can assume that non-literal arguments 
 are interspersed inside the literal.
It can be done, but it sounds more complex for the introspecting function. I'm not strict about this though, what the DIP now proposes be worth it to be able to pass `ref` parameters in interpolated strings.
 The type of interpolated string literal is very special cased. [snip]
I was fully aware that this would be the most controversial part. I feel like it will not be full of corner cases, but I'm not sure. Can you specify any? Consider a normal string literal can be used as a string, immutable(char)*, wstring, or dstring. I find it very similar to this feature, and I don't feel like there are a lot of corner cases there.
A string literal is a string that is implicitly assignable to the other alternatives via value range propagation mechanics, or that's how I understand it at least. The compromise that the interpolated string would be an expanded tuple, that would be implicitly assignable to string via value range propagation mechanics, sounds acceptable. But it needs to be clear IMO what the primary type of an interpolated string is. If it is not an expanded tuple, what it is then? I mean that this must be guaranteed to pass IMO:
Sorry, I'm late to the game here. This reminds me of slices vs static arrays. As a reminder, to a newcomer,      auto xs = [ 1, 2, 3 ]; looks like it would infer int[3] as the type of xs. It is obviously the most descriptive type for the literal. Why would it infer int[] forgetting its compile-time known length and even do an allocation? That seems so much worse. Even typeof([1,2,3]) is int[] and not int[3]. We know why D does it the way it does and goes int[3] with no allocation only if requested explicitly. You can do that with a template with a flexible length like this:      void takesStaticArray(size_t n)(int[n] staticArray); Here, `n` can usually be inferred from the argument. Interpolated strings could do the exact same thing: 1. make typeof(i"...") result to `string`. 2. make auto str = i"..." infer string (cf. typeof) and gc-allocate if necessary. 3. give i"..." a secondary type akin to [1,2,3] having int[3] as a secondary type.
I don't want to do it that way, because then the overload is not easy to ask for. I really really don't want the string form to be passed into a vararg template.
I think I understand you. I want the best of all worlds, too. Maybe I'm just not seeing it. As of now, I'm really convinced that, while it would be nice to have, it just would break too much intuition. Using something as simple and common as i"..." correctly MUST be trivial. One shouldn't have to look up how it works.
I get that position totally, and it's mostly where I'm coming from too. A string interpolation is meant to be a string first, and an expanded tuple if supported. I would be happy with a system where it's easy to specify that a function can accept interpolation tuples instead of strings as a variadic parameter.
 If an interpolated string is bound to a parameter of that secondary 
 type (`interp` in the DIP) it uses its secondary type (cf. calling 
 takesStaticArray with [1,2,3]). In any other case, e.g. `auto` or 
 otherwise generic template parameters will infer string.

 Getting a string is probably what most users expect most of the time. 
 Handling the secondary type must be explicit. It is almost an 
 implementation detail that shouldn't be exposed to the user too easily.
I'm not sure how this could be possible. You can't say T... and have it match the tuple over the string, unless the primary type is the tuple.
I'm not completely sure what that paragraph means.
I mean, let's say we wanted to make a function that doesn't just accept an interpolation tuple, it can accept multiple tuples. How do you write the signature for that? Or maybe you have optional parameters that look awkward inside the interpolation sequence? Once you get into variadic parameters, you have no power (except via template constraints) to say the interpolation tuple form should be used. Maybe in practice this isn't needed, but I am uneasy with no way to express that API. Note that I use this capability to explain why we don't need concatenation, because 2 tuples separated by a comma are still a tuple. Also, the fact that the tuple matches "Best effort" functions such as writeln and text (and probably a host of others, such as logging) I found to be very pleasing. Basically any place that accepts an untyped list of things to stringify. As pointed out, there are also other cases where an untyped list of things is used for non-stringy things (such as std.typecons.tuple). It might end up being, you just have to live with that use case.
 
 But if you have ideas, surely I'd prefer it to be a string first in 
 most cases!
I'd say the best idea is to make i"..." decay into string unless forced to be interp!".." (or really being a better match, really think about it like binding [1,2,3] to int[3]).
I read your post on the feedback thread, and it's definitely a different take than I had considered. Perhaps there is a way to do it, but for sure your mechanism of taking the string parameter of the first interp struct as a template parameter is a better mechanism than what I had thought of with template constraints.
 An easy way out would be giving the 
 actual type of i"..." a property .interp or .__interp that (cf. tuple's 
 expand) returns the interp sequence. That way, it can be tested in 
 pragma(msg, i"...".__interp) while also being correct whenever used in a 
 canonical form. __interp couldn't really be a library function. An 
 alternative would be __traits(interp, i"...").
Yeah, with your scheme, it would have to be a compiler directive somehow.
 The interpolation tuple really is an implementation detail that is 
 relevant for an important, but still a rather small minority of functions.
I'm not sure I see it that way. The interpolation tuple allows one to accept string/data lists without allocation or transformation. That is super-powerful and super useful. I would agree that the major use case that most people will use is just to allocate a string. -Steve
Feb 03 2021
parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
On Wednesday, 3 February 2021 at 23:00:54 UTC, Steven 
Schveighoffer wrote:
 On 2/3/21 4:00 PM, Q. Schroll wrote:
 An easy way out would be giving the actual type of i"..." a 
 property .interp or .__interp that (cf. tuple's expand) 
 returns the interp sequence. That way, it can be tested in 
 pragma(msg, i"...".__interp) while also being correct whenever 
 used in a canonical form. __interp couldn't really be a 
 library function. An alternative would be __traits(interp, 
 i"...").
Yeah, with your scheme, it would have to be a compiler directive somehow.
I don't think this is a problem.
 The interpolation tuple really is an implementation detail 
 that is relevant for an important, but still a rather small 
 minority of functions.
I'm not sure I see it that way. The interpolation tuple allows one to accept string/data lists without allocation or transformation. That is super-powerful and super useful.
My approach is not to take that power away. My concern is that it will hit you accidentally.
 I would agree that the major use case that most people will use 
 is just to allocate a string.
I've thought about the formal semantics of my suggestion. Formally, the compiler has to try the __interp and the string version. If both succeed (that's the relevant case) and instantiate THE SAME template, the string version (i.e. idup) will be used. Otherwise, the __interp is a better match and will be used. This is precisely the case when there is a template that matches interp sequences, but not strings. For supporting both, regular and interpolated strings, you need overloads, so that the interp sequence's match and the idup string's match is different. There was a question how to do `execute` for an SQL builder. First, one overload would handle interp sequences. With that exact semantics explained above, you (Steven in particular) can use a contract, too: auto execute(InterpSeq...)(InterpSeq interpSeq) if (is(InterpSeq[0] == interp!str, string str)) { /* handle interpolated string */ } auto execute(Args...)(string sqlTemplate, Args args) { /* handle regular sql template */ } That way, execute("SELECT $1 FROM table", name) only matches the second and behaves as intended; on the other hand, execute(i"SELECT ${name} FROM table") matches both: The first via execute(interp!"SELECT "(), name, interp!" FROM table"()) and the second via execute(text("SELECT ", name, " FROM table")). Because those are different templates, the first one is chosen that uses the interp sequence. If I'm not mistaken, you can even mix them: execute(i"SELECT $1 FROM ${table}", column) can be made work by the first overload. However, when you do tuple(i"I have ${nBananas} bananas", nBananas), both rewrites match the same template, so the rewrite tuple(text("I have ", nBananas, " bananas"), nBananas) is chosen. I don't think it can be made better without hitting usability. With the proposal as-is, one could use `static if` and other reflection in a single variadic template. Requiring a specific overload to handle interpolated strings is probably the most maintainable form anyway, so I don't consider that a big ask. With that semantics, free form variadic templates aren't instantiated in an unintended way. We cannot break all free form variadic templates and hope them to anticipate and handle interpolated strings. However, we can make writeln and friends use interpolated strings' powerful lowering and even if we happen to overlook a function that really should handle interpolated strings, it can be added later. Everyone wins.
Feb 04 2021
parent Q. Schroll <qs.il.paperinik gmail.com> writes:
On Thursday, 4 February 2021 at 18:14:59 UTC, Q. Schroll wrote:
 Formally, the compiler has to try the __interp and the string 
 version. If both succeed (that's the relevant case) and 
 instantiate THE SAME template, the string version (i.e. idup) 
 will be used. Otherwise, the __interp is a better match and 
 will be used.
I want to add how this works when using interpolated strings as template parameters. If templ is a template, templ!i"Hello ${name}, it's me!", also both rewrites are tried. If the only template is like this template templ(args...) { ... } it will make args.length == 1 and args[0] == "Hello "~name~", it's me!". To match an interp sequence, one needs an overload like this template templ(args...) if (args.length > 0 && is(typeof(args[0]) == interp!str, string str)) { ... } that matches no string, but an interp object. The case is relevant because one might want to handle the parts specially even though nogc and stuff is no concern for compile-time constructs. The same as aforementioned, you cannot have one template that fits all and decides later.
Feb 04 2021
prev sibling parent reply Meta <jared771 gmail.com> writes:
On Wednesday, 3 February 2021 at 16:52:23 UTC, Q. Schroll wrote:
 Getting a string is probably what most users expect most of the 
 time. Handling the secondary type must be explicit. It is 
 almost an implementation detail that shouldn't be exposed to 
 the user too easily.

 (I'll post that to the feedback thread as well.)
I 100% agree with this. Reading over the thread, it seems clear that we're trying to pessimize the most common use case to make system nogc usage possible... but this is counter to D's design philosophy of doing the correct/convenient thing by default, but also enabling more advanced use cases, potentially with a bit more effort. I think this is the way we should go. "istrings" (i"", iq{}, i``, etc.) should by default create a GC-managed string literal. This is by far the most intuitive and easy to understand thing to do, and if you're using istrings for code generation, you don't care that it's allocating memory. However, if you want a tuple sequence as described in the DIP, you can simply call a (probably compiler-supplied) helper method; maybe it doesn't even have to be new: why not .tupleof? auto apples = 2; auto bananas = 3; auto s1 = i"I have ${apples + bananas} fruit"; static assert(is(typeof(s1) == string)); auto s2 = i"I have ${apples + bananas} fruit".tupleof; Then we can hide all the complexity of interpolated sequences behind the .tupleof (or whatever we decide on) magic property.
Feb 04 2021
next sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 4 February 2021 at 16:15:03 UTC, Meta wrote:
 I 100% agree with this. Reading over the thread, it seems clear 
 that we're trying to pessimize the most common use case
How often do you just create a string and not actually do anything with it? I'm very skeptical most this stuff will actually matter in practice since most cases will be used in function calls.
Feb 04 2021
next sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Thursday, 4 February 2021 at 16:27:32 UTC, Adam D. Ruppe wrote:
 I'm very skeptical most this stuff will actually matter in 
 practice since most cases will be used in function calls.
I think it would be better to improve on templates/CTFE so that interpolated strings can be implemented as pure meta programming. Basically some way to say that a prefix for a string matches up with a certain type of template (by name or other means). Then some way to emit mixins into the calling context, maybe some way to query the calling context. So if you write: writeln(f`Four is {1+3}!`) The lookup will hit some templated construct f__interpolate__!…. writeln(f__interpolate__!`Four is {1+3}!`()) Which has special capabilities that allows it to emit: writeln(mixin(`tuple("Four is ", (1+3).to!string(), "!").expand`)); D does not need more builtins, improve on pure meta programming instead seems more productive.
Feb 04 2021
parent sighoya <sighoya gmail.com> writes:
On Thursday, 4 February 2021 at 16:45:14 UTC, Ola Fosheim Grøstad 
wrote:
 So if you write:

   writeln(f`Four is {1+3}!`)

 The lookup will hit some templated construct f__interpolate__!….

   writeln(f__interpolate__!`Four is {1+3}!`())

 Which has special capabilities that allows it to emit:

   writeln(mixin(`tuple("Four is ", (1+3).to!string(), 
 "!").expand`));


 D does not need more builtins, improve on pure meta programming 
 instead seems more productive.
Agree, but we aren't there yet. Personally, I would prefer f"$someVar" being rewritten to f!"$someVar" such that the whole logic, including custom parsing, can be implemented as a pure D function. It requires however the access to the caller's context in order to seek for the missing parameters. Moreover, further support from the frontend is needed such as throwing custom compiler errors and so on…
Feb 04 2021
prev sibling parent reply Meta <jared771 gmail.com> writes:
On Thursday, 4 February 2021 at 16:27:32 UTC, Adam D. Ruppe wrote:
 On Thursday, 4 February 2021 at 16:15:03 UTC, Meta wrote:
 I 100% agree with this. Reading over the thread, it seems 
 clear that we're trying to pessimize the most common use case
How often do you just create a string and not actually do anything with it? I'm very skeptical most this stuff will actually matter in practice since most cases will be used in function calls.
Your response confuses me a bit, so can you define "not actually do anything with it"? You'd do all the normal things you do with strings. Print them, parse them, replace sequences, etc.
Feb 04 2021
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 4 February 2021 at 17:04:20 UTC, Meta wrote:
 Your response confuses me a bit, so can you define "not 
 actually do anything with it"? You'd do all the normal things 
 you do with strings. Print them, parse them, replace sequences, 
 etc.
So you'd pass them to functions. Which, with a mature library ecosystem, doesn't actually need anything beyond the naked tuple! It would just work without any conversions. Heck, functions like `writeln` and `text` already just work in the POC with no implicit conversions at all, no explicit library overlaods. And you can use those to do the translation anyway when you do need it.
Feb 04 2021
next sibling parent reply Meta <jared771 gmail.com> writes:
On Thursday, 4 February 2021 at 17:14:31 UTC, Adam D. Ruppe wrote:
 On Thursday, 4 February 2021 at 17:04:20 UTC, Meta wrote:
 Your response confuses me a bit, so can you define "not 
 actually do anything with it"? You'd do all the normal things 
 you do with strings. Print them, parse them, replace 
 sequences, etc.
So you'd pass them to functions. Which, with a mature library ecosystem, doesn't actually need anything beyond the naked tuple! It would just work without any conversions. Heck, functions like `writeln` and `text` already just work in the POC with no implicit conversions at all, no explicit library overlaods. And you can use those to do the translation anyway when you do need it.
I think it comes down to, when people (primarily and especially new users) see "", they expect a string. To get a weird (to them) tuple thing instead is just not a good user experience. Even if libraries can be made to work with "auto-tuple" strings, I really don't think we should be asking them to participate in this façade; it's an illusion that breaks on cursory inspection. IMO it should be strings first, tuples if you ask.
Feb 04 2021
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 4 February 2021 at 17:50:56 UTC, Meta wrote:
 I think it comes down to, when people (primarily and especially 
 new users) see "", they expect a string.
It isn't "", it is i"". Javascript programmers seem to be able to figure out the concept that a different prefix may not yield a string. I'm sure D programmers can figure it out too. Now my view btw is I'm down to two options: 1) You *always* get the naked tuple. It never converts to string, but of course, you can call a function to convert it to string. int bar; auto item = i"foo ${bar}"; typeof(item) == AliasSeq!(interp!"foo ", int); string s = item; // compile error, type mismatch This is what I already implemented so you can play with it immediately. 2) You *never* get the naked tuple. It is immediately passed to an object constructor. That object uses `alias toString this` to handle the string conversions and functions that want the whole thing just overload on that. typeof(item) == InterpolatedObject!(interp!"foo ", int) string s = item; // works under current rules because InterpolatedObject has alias toString this. Adding this to the existing implementation is fairly simple since it is the same except a ctor wrapper and one more druntime type. Pros of 1: maximum flexibility. It can do anything you can imagine by function calls, delegating 100% to user libraries. No unnecessary strings built. Pros of 2: more traditional explanation, the implicit cast works under existing language rules. Cons of 1: if you want a string, you *must* ask for one. Cons of 2: sacrifices whatever potential there was in alias, ref. has quirks with inout, may not work in all cases with dip1000, dip 25, etc. It is basically a 90% solution. Also will require more library support (that toString has to call something, much of that is already in druntime, or it could import Phobos, so it isn't a big deal, but it does need to be there for the implicit conversion to work). I've lost interest in anything else.
Feb 04 2021
parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Thursday, 4 February 2021 at 18:50:17 UTC, Adam D. Ruppe wrote:
 On Thursday, 4 February 2021 at 17:50:56 UTC, Meta wrote:
 [...]
It isn't "", it is i"". Javascript programmers seem to be able to figure out the concept that a different prefix may not yield a string. I'm sure D programmers can figure it out too. [...]
+1 for 1
Feb 05 2021
prev sibling parent sighoya <sighoya gmail.com> writes:
I feel it mentally easier to treat them as strings at compile 
time, too.

Can someone convince me why we are required to use tuples for 
compile time interpolations?

Is it owed to defer the process of interpolation after template 
expansion s.t. interpolation refers to the context inside the 
template instead of outside:

```
someTemplate!(i"${Hello}${World}")() //$Hello and $World will be 
substituted inside someTemplate
```

Can't we alternatively introduce another kind of string operator 
allowing us to defer string interpolation up to the considered 
place of substitution via the use of some closure?:

```
void someInterpolateFun(string function(string,string) 
intermediate)
{
     string s1=...
     string s2=...
     return intermediate(s1,s2)
}

string function(string Hello,string World) intermediate = 
di"${Hello}${World}" //di stand for delegate/defer + interpolate 
and creates a closure delegating the parameters into the 
interpolation.
someInterpolateFun(intermediate)
```
Feb 04 2021
prev sibling next sibling parent reply Arafel <er.krali gmail.com> writes:
On 4/2/21 17:15, Meta wrote:
 However, if you want a tuple sequence as described in the DIP, you can 
 simply call a (probably compiler-supplied) helper method; maybe it 
 doesn't even have to be new: why not .tupleof?
 
 auto apples = 2;
 auto bananas = 3;
 
 auto s1 = i"I have ${apples + bananas} fruit";
 static assert(is(typeof(s1) == string));
 
 auto s2 = i"I have ${apples + bananas} fruit".tupleof;
 
 Then we can hide all the complexity of interpolated sequences behind the 
 .tupleof (or whatever we decide on) magic property.
Could this be made so it's transparent to the user of a library? I.e. in the SQL case, the user of a library just types: ``` auto result = connection.execute(i"SELECT * FROM foo WHERE bar = ${baz}"); ``` instead of ``` auto result = connection.execute(i"SELECT * FROM foo WHERE bar = ${baz}".tupleof); ``` If it has to be explicit, how would the signature of Connection.execute(...) look like? How would the user easily know that the function expects an interpolated literal, just by looking at it?
Feb 04 2021
parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
On Thursday, 4 February 2021 at 16:31:17 UTC, Arafel wrote:
 On 4/2/21 17:15, Meta wrote:
 However, if you want a tuple sequence as described in the DIP, 
 you can simply call a (probably compiler-supplied) helper 
 method; maybe it doesn't even have to be new: why not .tupleof?
 
 auto apples = 2;
 auto bananas = 3;
 
 auto s1 = i"I have ${apples + bananas} fruit";
 static assert(is(typeof(s1) == string));
 
 auto s2 = i"I have ${apples + bananas} fruit".tupleof;
 
 Then we can hide all the complexity of interpolated sequences 
 behind the .tupleof (or whatever we decide on) magic property.
Could this be made so it's transparent to the user of a library? I.e. in the SQL case, the user of a library just types: ``` auto result = connection.execute(i"SELECT * FROM foo WHERE bar = ${baz}"); ``` instead of ``` auto result = connection.execute(i"SELECT * FROM foo WHERE bar = ${baz}".tupleof); ``` If it has to be explicit, how would the signature of Connection.execute(...) look like? How would the user easily know that the function expects an interpolated literal, just by looking at it?
When Adam D. Ruppe wrote about SQL injections, I thought about that. The signature of `execute` must not take a string as the only parameter for sure. This is the basic pattern for accepting an interpolated sequence. auto execute(string str, Args...)(interp!str first, Args rest) { } If you call `execute` with `execute(i"SELECT ${name} FROM table"), the compiler tries i"SELECT ${name} FROM table".tupleof and finds a PERFECT match to the above template. If that `execute` is the only one available, you cannot use it with something other than an interpolated string as the first parameter; nothing else (like a regular string literal) will match `interp!str`.
Feb 04 2021
parent reply Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Thursday, 4 February 2021 at 17:39:50 UTC, Q. Schroll wrote:
 On Thursday, 4 February 2021 at 16:31:17 UTC, Arafel wrote:
 [...]
When Adam D. Ruppe wrote about SQL injections, I thought about that. The signature of `execute` must not take a string as the only parameter for sure. This is the basic pattern for accepting an interpolated sequence. auto execute(string str, Args...)(interp!str first, Args rest) { } If you call `execute` with `execute(i"SELECT ${name} FROM table"), the compiler tries i"SELECT ${name} FROM table".tupleof and finds a PERFECT match to the above template. If that `execute` is the only one available, you cannot use it with something other than an interpolated string as the first parameter; nothing else (like a regular string literal) will match `interp!str`.
everyone: We discuss more in depth on Discord. If you want to you could join the server and flesh out your ideas there. Easier to clarify various concepts imo.
Feb 04 2021
parent reply sighoya <sighoya gmail.com> writes:
On Thursday, 4 February 2021 at 17:49:06 UTC, Imperatorn wrote:

  everyone:
 We discuss more in depth on Discord. If you want to you could 
 join the server and flesh out your ideas there. Easier to 
 clarify various concepts imo.
Which channel?
Feb 04 2021
parent reply Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Thursday, 4 February 2021 at 18:13:04 UTC, sighoya wrote:
 On Thursday, 4 February 2021 at 17:49:06 UTC, Imperatorn wrote:

  everyone:
 We discuss more in depth on Discord. If you want to you could 
 join the server and flesh out your ideas there. Easier to 
 clarify various concepts imo.
Which channel?
Mostly programming or dev_urandom
Feb 04 2021
parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
On Thursday, 4 February 2021 at 19:14:21 UTC, Imperatorn wrote:
 On Thursday, 4 February 2021 at 18:13:04 UTC, sighoya wrote:
 On Thursday, 4 February 2021 at 17:49:06 UTC, Imperatorn wrote:

  everyone:
 We discuss more in depth on Discord. If you want to you could 
 join the server and flesh out your ideas there. Easier to 
 clarify various concepts imo.
Which channel?
Mostly programming or dev_urandom
Discord is external and cannot be Google searched. A DIP discussion should be archived for reference. How would I cite a Discord message in a DIP? How would I know it exists? TL:DR: No.
Feb 04 2021
next sibling parent reply Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Thursday, 4 February 2021 at 19:57:22 UTC, Q. Schroll wrote:
 On Thursday, 4 February 2021 at 19:14:21 UTC, Imperatorn wrote:
 On Thursday, 4 February 2021 at 18:13:04 UTC, sighoya wrote:
 On Thursday, 4 February 2021 at 17:49:06 UTC, Imperatorn 
 wrote:

  everyone:
 We discuss more in depth on Discord. If you want to you 
 could join the server and flesh out your ideas there. Easier 
 to clarify various concepts imo.
Which channel?
Mostly programming or dev_urandom
Discord is external and cannot be Google searched. A DIP discussion should be archived for reference. How would I cite a Discord message in a DIP? How would I know it exists? TL:DR: No.
You missed my everyone. It was general information.
Feb 04 2021
parent Mike Parker <aldacron gmail.com> writes:
On Thursday, 4 February 2021 at 21:06:49 UTC, Imperatorn wrote:
 On Thursday, 4 February 2021 at 19:57:22 UTC, Q. Schroll wrote:
 Discord is external and cannot be Google searched. A DIP 
 discussion should be archived for reference. How would I cite 
 a Discord message in a DIP? How would I know it exists?

 TL:DR: No.
You missed my everyone. It was general information.
everyone: This is way off topic. Please keep the discussion focused on the contents of the DIP. Thank!
Feb 04 2021
prev sibling parent reply Max Haughton <maxhaton gmail.com> writes:
On Thursday, 4 February 2021 at 19:57:22 UTC, Q. Schroll wrote:
 On Thursday, 4 February 2021 at 19:14:21 UTC, Imperatorn wrote:
 On Thursday, 4 February 2021 at 18:13:04 UTC, sighoya wrote:
 On Thursday, 4 February 2021 at 17:49:06 UTC, Imperatorn 
 wrote:

  everyone:
 We discuss more in depth on Discord. If you want to you 
 could join the server and flesh out your ideas there. Easier 
 to clarify various concepts imo.
Which channel?
Mostly programming or dev_urandom
Discord is external and cannot be Google searched. A DIP discussion should be archived for reference. How would I cite a Discord message in a DIP? How would I know it exists? TL:DR: No.
DIP discussions are also just that. Most of the threads is just bikeshedding or dominated by single issues - regardless of where, having branching discussions of DIPs would be a good thing in the long run, a single forum thread is often not optimal as evidenced by the absolutely enormous DIP1036 thread. If it's important it should go in the review thread. And discord is easily searchable, easier than searching the forums.
Feb 04 2021
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Feb 04, 2021 at 09:29:32PM +0000, Max Haughton via Digitalmars-d wrote:
[...]
 And discord is easily searchable, easier than searching the forums.
Not IME. Discord's linear UI (like Slack, phpBB, and many others of its ilk) is absolutely painful for searching, even more so for following long-winded discussions, whereas using a fully-threaded mail reader with the mail interface to the forums (or, for that matter, a full-fledged threaded NNTP client) that shows the threading structure of the discussion is *much* easier to follow. It's a problem that has been solved since the ancient days of Usenet, but modern discussion platforms just keep reinventing the wheel poorly, or worse, not at all and ending up being completely useless. T -- They say that "guns don't kill people, people kill people." Well I think the gun helps. If you just stood there and yelled BANG, I don't think you'd kill too many people. -- Eddie Izzard, Dressed to Kill
Feb 04 2021
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 2/4/21 11:15 AM, Meta wrote:
 However, if you want a tuple sequence as described in the DIP, you can 
 simply call a (probably compiler-supplied) helper method; maybe it 
 doesn't even have to be new: why not .tupleof?
Wow, I can't believe I didn't think of this. This makes things a bit easier to reason about, because now we have a way to explicitly form a string and a way to explicitly form a tuple. I just tested, and "string".tupleof is an error, so that fits perfectly. I have to confer with my partner in crime, but thank you for this nugget, and I really like the direction with the discussion here. -Steve
Feb 04 2021
parent Meta <jared771 gmail.com> writes:
On Thursday, 4 February 2021 at 22:34:37 UTC, Steven 
Schveighoffer wrote:
 On 2/4/21 11:15 AM, Meta wrote:
 However, if you want a tuple sequence as described in the DIP, 
 you can simply call a (probably compiler-supplied) helper 
 method; maybe it doesn't even have to be new: why not .tupleof?
Wow, I can't believe I didn't think of this. This makes things a bit easier to reason about, because now we have a way to explicitly form a string and a way to explicitly form a tuple. I just tested, and "string".tupleof is an error, so that fits perfectly. I have to confer with my partner in crime, but thank you for this nugget, and I really like the direction with the discussion here. -Steve
Glad you found it useful!
Feb 04 2021
prev sibling parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Thursday, 4 February 2021 at 16:15:03 UTC, Meta wrote:
 However, if you want a tuple sequence as described in the DIP, 
 you can simply call a (probably compiler-supplied) helper 
 method; maybe it doesn't even have to be new: why not .tupleof?

 auto apples = 2;
 auto bananas = 3;

 auto s1 = i"I have ${apples + bananas} fruit";
 static assert(is(typeof(s1) == string));

 auto s2 = i"I have ${apples + bananas} fruit".tupleof;

 Then we can hide all the complexity of interpolated sequences 
 behind the .tupleof (or whatever we decide on) magic property.
IMO, if I pass an i"I have ${apples + bananas} fruit" to a function, and it allocates memory, this feature completely misses its purpose and I can't see where I'd use it. Right now, we're using a *lot* of format calls of the form `template function!"format string"(arguments), and the fact that these calls are high-performance and don't involve the GC is a massive argument in their favor. In extreme cases we can end up pushing megabytes of data every second through functions like this. Demanding tupleof for every such call is such an awkward syntax that I'd honestly rather not use the feature at all. To me the tuple form is the primary usecase, not the string form. I understand that people think developers want something simple first and foremost, but if the complex form is the *only form I'd be interested in using*, then something's wrong.
Feb 08 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 2/8/21 6:46 AM, FeepingCreature wrote:
 On Thursday, 4 February 2021 at 16:15:03 UTC, Meta wrote:
 However, if you want a tuple sequence as described in the DIP, you can 
 simply call a (probably compiler-supplied) helper method; maybe it 
 doesn't even have to be new: why not .tupleof?

 auto apples = 2;
 auto bananas = 3;

 auto s1 = i"I have ${apples + bananas} fruit";
 static assert(is(typeof(s1) == string));

 auto s2 = i"I have ${apples + bananas} fruit".tupleof;

 Then we can hide all the complexity of interpolated sequences behind 
 the .tupleof (or whatever we decide on) magic property.
IMO, if I pass an i"I have ${apples + bananas} fruit" to a function, and it allocates memory, this feature completely misses its purpose and I can't see where I'd use it. Right now, we're using a *lot* of format calls of the form `template function!"format string"(arguments), and the fact that these calls are high-performance and don't involve the GC is a massive argument in their favor. In extreme cases we can end up pushing megabytes of data every second through functions like this. Demanding tupleof for every such call is such an awkward syntax that I'd honestly rather not use the feature at all.
I would also not use a feature where .tupleof or some other syntax is needed to do what is needed (in major use cases). The goal here is to simplify syntax, we already have the comma-quote-spam form. However, I do like the mechanism of .tupleof for explicitly requesting the tuple form where it makes sense. So an important part of this DIP that will NOT change is that you should be able to accept the tuple form via overload without having to tack on a converter or property.
 
 To me the tuple form is the primary usecase, not the string form. I 
 understand that people think developers want something simple first and 
 foremost, but if the complex form is the *only form I'd be interested in 
 using*, then something's wrong.
I think we can have both use cases, with overloads declaring somehow they accept the tuple form, and have it usable as a string form in other cases. For sure, the primary use case for me is the tuple form. But there have been several users who are heavy users of the D language that have also declared that their primary use case is the string form (and in some cases have said they will NEVER use the tuple form). If we don't have both available, the feature feels incomplete. My goal is to have both. And I think we can have both. I think this DIP as written covers both. The tricky part is, what is the *default* for functions that accept tuples but are not written to accept the tuple form. That is where a complete confusion would happen. And I am in agreement with people here that having the thing change forms when called on a variadic template, while workable, is not ideal. So I think the next update, we will have a more definite mechanism to accept the tuple form without user intervention, and a more solid rule for when the idup rewrite happens. This review is almost done, and it looks likely a 3rd review will have to happen if I make significant changes. But this is what these reviews are for. I'll post another thread when I put up the PR for the next changes and we can discuss there. -Steve
Feb 08 2021
parent FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 8 February 2021 at 14:24:35 UTC, Steven Schveighoffer 
wrote:
 On 2/8/21 6:46 AM, FeepingCreature wrote:
 
 IMO, if I pass an i"I have ${apples + bananas} fruit" to a 
 function, and it allocates memory, this feature completely 
 misses its purpose and I can't see where I'd use it.
I would also not use a feature where .tupleof or some other syntax is needed to do what is needed (in major use cases). The goal here is to simplify syntax, we already have the comma-quote-spam form. However, I do like the mechanism of .tupleof for explicitly requesting the tuple form where it makes sense. So an important part of this DIP that will NOT change is that you should be able to accept the tuple form via overload without having to tack on a converter or property.
Thanks, that's a big relief! As long as the tuple form can be recognized by a template overload, I for one am completely satisfied.
Feb 08 2021
prev sibling next sibling parent reply Paul Backus <snarwin gmail.com> writes:
Continuing from the feedback thread...

On Friday, 29 January 2021 at 16:55:05 UTC, Steven Schveighoffer 
wrote:
 On 1/28/21 6:06 PM, Paul Backus wrote:
 As with any heuristic or approximation, there are edge cases 
 where this breaks down. One of them is called out in the DIP 
 itself--type inference via `auto`--but it is not hard to 
 imagine others. For example, a programmer who writes
 
      tuple(i"Good morning ${name}", i"Good evening ${name}")
 
 ...is probably not going to get what they intended, even 
 though their code compiles.
This is quite the unique edge case though. Any proposal that provides the flexible version is going to have trouble with tuple as it is now.
You are missing the forest for the trees here. My criticism of DIP 1036 is not that it has trouble with `tuple`. My criticism is that DIP 1036 attempts to guess what the programmer wants, because guessing what the programmer wants is bad language design.
 There are solutions that can be had. For instance, tuple could 
 be instrumented never to accept parameters that contain 
 interpolation literals. Therefore, the idup rewrite happens, 
 and they get what they expect. This is not hard to solve.
What was it Andrei said about this kind of thing? Ah, right: Good Work begets more Good Work. Typically Good Work produces context, opportunity, and precedent for more of the same. The same reviewer who rubber stamped a piece of Good Work will have an idea how to produce more Good Work derived from it. The kind of environment where Good Work is revered encourages its creation, in a cycle that creates the illusion of progress. Because Good Work is complex, it produces "bug ripples" whereby increasingly complex Good Work fixes one bug but is liable to introduce others. [1] When you find yourself saying things like "it's fine, we can just add special cases to printf...and tuple...and mixin...", that's a sure sign that something has gone wrong in your language-feature design.
 Every D programmer who wants to make effective use of DIP 
 1036's interpolation literals will have to go through the 
 process of learning when .idup is required, when it's 
 optional, when it's allowed-but-unnecessary, and when it's 
 forbidden--which means that, in practice, they will have to 
 learn how it actually works, under the hood.
This is not my interpretation at all. I can't think of a reasonable case aside from your tuple example where idup is required (if that's what you want). Can you?
Sure, here's another one: anySatisfy!(isSomeString, i"I have ${count} apples") Hopefully this is enough to satisfy you that it's your imagination that's failing you here, not my reasoning.
 This is not a desirable trait for a language feature that's 
 intended to make programming *easier*.
Your logic is not very sound. It's ironic to say the language doing what you expect for 99% of cases is a higher burden than requiring you to write it yourself for 100% of cases.
A language that does unexpected things 1% of the time is much worse than a language that does exactly what I tell it to 100% of the time.
 Ultimately, I think attempting to guess the programmer's 
 intent is the wrong way to go here. Either force them to spell 
 it out explicitly (with a call to .idup, .text, etc.), or take 
 away the choice and give up on one of the two approaches.
I want to say something about this idea of doing only one or the other.
To be clear: you can still have both, just not with the same syntax. If you want both, my recommendation would be to use the i"..." syntax for the convenient string version, and qq"..." ("quasiquote") for the flexible tuple version. At that point, it would probably make sense to split them into two separate DIPs, too. [1] https://forum.dlang.org/post/q7u6g1$94p$1 digitalmars.com
Jan 29 2021
next sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 29 January 2021 at 18:49:18 UTC, Paul Backus wrote:
 To be clear: you can still have both, just not with the same 
 syntax.
Steven and I argued for a little over this topic. I was of the opinion that only the tuple return is necessary - it is trivial to convert to string on demand by calling a function. The argument that swayed me is that user habit will start to be wrong. Look at what people do with .array on ranges. It is used all over the place, including in places where it is pointless. People recommend it out of habit, not out of consideration. .array is basically harmless though. Using it unnecessarily just wastes time and memory while still doing the right thing. On the other hand, with these interpolated results, misusing it results in potentially serious problems, like sql injection, that library code may not be able to warn about*. So we really don't want it to be an unthinking habit. The more cases where plain i"" does the right thing, the more likely the user is to actually use it instead of developing harmful habits. And while you're arguing in this thread, the other threads had a lot of people saying the basic case just working is important to them. Since retaining Python users is a solid growth area for D, I do think we should listen to them and reduce their initial friction. I still do agree this implicit conversion part is certainly more complex than the rest of the proposal combined and still have some reservations myself, but it would be nice to have. Either way, whether it is there or not, some user education is required for them to understand that i"" is not *really* some string, but rather a string builder (just perhaps with some implicit conversions). I don't believe this is a dealbreaker. It is just like how we have to educate users about value range propagation and range semantics and such. A lot of knowledge carries over, but it isn't exactly the same to other languages because D wants to leverage its unique strengths. * one option would be to not have a string overload *at all* in the library, or at least give it an ugly name or something. The big problem there is just doing it in a way that's compatible with today's D, but it certainly is worth considering.
Jan 29 2021
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 1:49 PM, Paul Backus wrote:
 Continuing from the feedback thread...
 
 On Friday, 29 January 2021 at 16:55:05 UTC, Steven Schveighoffer wrote:
 This is not my interpretation at all. I can't think of a reasonable 
 case aside from your tuple example where idup is required (if that's 
 what you want). Can you?
Sure, here's another one:     anySatisfy!(isSomeString, i"I have ${count} apples")
It would be a compiler error, just like it is for a normal string. -Steve
Jan 29 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 29 January 2021 at 19:20:59 UTC, Steven Schveighoffer 
wrote:
 On 1/29/21 1:49 PM, Paul Backus wrote:
 Continuing from the feedback thread...
 
 On Friday, 29 January 2021 at 16:55:05 UTC, Steven 
 Schveighoffer wrote:
 This is not my interpretation at all. I can't think of a 
 reasonable case aside from your tuple example where idup is 
 required (if that's what you want). Can you?
Sure, here's another one:     anySatisfy!(isSomeString, i"I have ${count} apples")
It would be a compiler error, just like it is for a normal string. -Steve
Ok, you got me, I forgot a `typeof`: anySatisfy!(isSomeString, typeof(i"I have ${count} apples"))
Jan 29 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 2:33 PM, Paul Backus wrote:
 On Friday, 29 January 2021 at 19:20:59 UTC, Steven Schveighoffer wrote:
 On 1/29/21 1:49 PM, Paul Backus wrote:
 Continuing from the feedback thread...

 On Friday, 29 January 2021 at 16:55:05 UTC, Steven Schveighoffer wrote:
 This is not my interpretation at all. I can't think of a reasonable 
 case aside from your tuple example where idup is required (if that's 
 what you want). Can you?
Sure, here's another one:      anySatisfy!(isSomeString, i"I have ${count} apples")
It would be a compiler error, just like it is for a normal string. -Steve
Ok, you got me, I forgot a `typeof`:     anySatisfy!(isSomeString, typeof(i"I have ${count} apples"))
Returns true. -Steve
Jan 29 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 29 January 2021 at 19:35:55 UTC, Steven Schveighoffer 
wrote:
 On 1/29/21 2:33 PM, Paul Backus wrote:
 
 Ok, you got me, I forgot a `typeof`:
 
      anySatisfy!(isSomeString, typeof(i"I have ${count} 
 apples"))
Returns true. -Steve
Not the way the DIP is currently written. `typeof` can accept an expression tuple, and evaluates to a type tuple when it does; e.g., static assert(is(typeof(AliasSeq!(1, 2.0, "hello")) == AliasSeq!(int, double, string)));
Jan 29 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 2:42 PM, Paul Backus wrote:
 On Friday, 29 January 2021 at 19:35:55 UTC, Steven Schveighoffer wrote:
 On 1/29/21 2:33 PM, Paul Backus wrote:
 Ok, you got me, I forgot a `typeof`:

      anySatisfy!(isSomeString, typeof(i"I have ${count} apples"))
Returns true.
Not the way the DIP is currently written. `typeof` can accept an expression tuple, and evaluates to a type tuple when it does; e.g.,     static assert(is(typeof(AliasSeq!(1, 2.0, "hello")) == AliasSeq!(int, double, string)));
https://github.com/dlang/DIPs/blob/344e00ee2d6683d61ee019d5ef6c1a0646570093/DIPs/DIP1036.md#a-string-interpolation-sequence-is-not-a-value-tuple The intention is for anything other than a function call or template that accepts the expanded form to treat it as a string type. This means typeof too. I should add it to the examples there. It's like a polysemous string literal: void foo(immutable char *); foo("hello"); // ok, compiler makes it work pragma(msg, typeof("hello")); // string, not immutable char *; auto x = "hello"; foo(x); // error -Steve
Jan 29 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 29 January 2021 at 19:51:52 UTC, Steven Schveighoffer 
wrote:
 https://github.com/dlang/DIPs/blob/344e00ee2d6683d61ee019d5ef6c1a0646570093/DIPs/DIP1036.md#a-string-interpolation-sequence-is-not-a-value-tuple

 The intention is for anything other than a function call or 
 template that accepts the expanded form to treat it as a string 
 type. This means typeof too. I should add it to the examples 
 there.
That just pushes the problem back one level: anySatisfy!(isSomeString, typeof(AliasSeq!(i"I have ${count} applies"))) Or, more realistically: template someTemplate(Args...) { static if (anySatisfy!(isSomeString, typeof(Args)) { // ... } } someTemplate!(i"I have ${count} apples")
Jan 29 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 3:01 PM, Paul Backus wrote:
 On Friday, 29 January 2021 at 19:51:52 UTC, Steven Schveighoffer wrote:
 https://github.com/dlang/DIPs/blob/344e00ee2d6683d61ee019d5ef6c1a0646570093/DIPs/DIP1036.md#a-string-interpolation-sequence-
s-not-a-value-tuple 


 The intention is for anything other than a function call or template 
 that accepts the expanded form to treat it as a string type. This 
 means typeof too. I should add it to the examples there.
That just pushes the problem back one level:     anySatisfy!(isSomeString, typeof(AliasSeq!(i"I have ${count} applies"))) Or, more realistically:     template someTemplate(Args...) {         static if (anySatisfy!(isSomeString, typeof(Args)) {             // ...         }     }     someTemplate!(i"I have ${count} apples")
Yes, that would return false. But, this seems still pretty far fetched for a real use case. Not only that, but there is still a way to fix it, just use .idup if what you really meant was a string. And it's not something that's needed to be done by the author of someTemplate, just the user in the (probably one) case that he uses it. Consider that there are still places where one must use AliasSeq!(things) to work with them properly in templates (I get bit by this occasionally). It's quite similar actually. I remain unconvinced that this is a problem. But I will concede there are a few more cases where an explicit idup might be required than just tuple. -Steve
Jan 29 2021
parent reply Jacob Carlborg <doob me.com> writes:
On 2021-01-29 21:45, Steven Schveighoffer wrote:

 Yes, that would return false.
 
 But, this seems still pretty far fetched for a real use case. Not only 
 that, but there is still a way to fix it, just use .idup if what you 
 really meant was a string. And it's not something that's needed to be 
 done by the author of someTemplate, just the user in the (probably one) 
 case that he uses it.
 
 Consider that there are still places where one must use 
 AliasSeq!(things) to work with them properly in templates (I get bit by 
 this occasionally). It's quite similar actually.
 
 I remain unconvinced that this is a problem. But I will concede there 
 are a few more cases where an explicit idup might be required than just 
 tuple.
You could tweak the DIP slightly and say: in all places `idup` is automatically inserted, except in a parameter list where the first parameter is `interp`. template someTemplate(Args...) { static if (anySatisfy!(isSomeString, typeof(Args)) { // ... } } someTemplate!(i"I have ${count} apples"); The above would evaluate to true. template someTemplate2(interp!string i, Args...) { static if (anySatisfy!(isSomeString, typeof(Args)) { // ... } } someTemplate2!(i"I have ${count} apples"); The above would evaluate to false. -- /Jacob Carlborg
Jan 29 2021
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/30/21 2:36 AM, Jacob Carlborg wrote:
 On 2021-01-29 21:45, Steven Schveighoffer wrote:
 
 Yes, that would return false.

 But, this seems still pretty far fetched for a real use case. Not only 
 that, but there is still a way to fix it, just use .idup if what you 
 really meant was a string. And it's not something that's needed to be 
 done by the author of someTemplate, just the user in the (probably 
 one) case that he uses it.

 Consider that there are still places where one must use 
 AliasSeq!(things) to work with them properly in templates (I get bit 
 by this occasionally). It's quite similar actually.

 I remain unconvinced that this is a problem. But I will concede there 
 are a few more cases where an explicit idup might be required than 
 just tuple.
You could tweak the DIP slightly and say: in all places `idup` is automatically inserted, except in a parameter list where the first parameter is `interp`. template someTemplate(Args...) {     static if (anySatisfy!(isSomeString, typeof(Args)) {         // ...     } } someTemplate!(i"I have ${count} apples"); The above would evaluate to true. template someTemplate2(interp!string i, Args...) {     static if (anySatisfy!(isSomeString, typeof(Args)) {         // ...     } } someTemplate2!(i"I have ${count} apples"); The above would evaluate to false.
No, I don't want to do this. It's possible, but very very unintuitive, especially when the expanded form MUST match a variadic parameter The more we special case these rules, the harder to understand it gets. And besides, your first parameter is not right. There's actually no way to specify it outside a template constraint I think. -Steve
Jan 31 2021
prev sibling next sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 1:49 PM, Paul Backus wrote:
 When you find yourself saying things like "it's fine, we can just add 
 special
 cases to printf...and tuple...and mixin...", that's a sure sign that 
 something
 has gone wrong in your language-feature design.
This seems to suggest we aren't done when the DIP is passed. That isn't the case. No special cases are needed. mixins will work, printf will not (unless you want to write a wrapper). tuple doesn't need a special case, it will do what you ask, you have to ask it the write thing. These forums are full of "how do I do X with D?" and someone answers and they say "Thanks" and move on, having learned something. It is no different here. People have asked "You can't use printf with it", and I answered the question. I'm not saying "hey, let's add a printf wrapper to phobos and only then this DIP will be complete", I'm saying "if for some reason you want to use it with printf, here's how you would do it". It seems we are in a trap where someone says "yeah but you can't do this", and answering that question "no, you're right" means it's a failed feature. Answering that question with "yes, here's how" ALSO means it's a failed feature. -Steve
Jan 29 2021
prev sibling parent claptrap <clap trap.com> writes:
On Friday, 29 January 2021 at 18:49:18 UTC, Paul Backus wrote:
 Continuing from the feedback thread...
 I want to say something about this idea of doing only one or 
 the other.
To be clear: you can still have both, just not with the same syntax. If you want both, my recommendation would be to use the i"..." syntax for the convenient string version, and qq"..." ("quasiquote") for the flexible tuple version. At that point, it would probably make sense to split them into two separate DIPs, too. [1] https://forum.dlang.org/post/q7u6g1$94p$1 digitalmars.com
https://forum.dlang.org/post/rjih3p$vj2$1 digitalmars.com
Jan 30 2021
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
In reply to Steve in the Feedback thread:

 in DIP1027, formatting was a central component of interpolation. What became 
clear as the prior version was reviewed was that the complexity of specifying format while transforming into a parameter sequence was not worth adding to the language. Not clear to me that ${?} is that complex, and the language itself did not need to know what the format was, it just had to fit inside the ${ }.
 In particular, many applications of string interpolation did not fit well 
with format specifications (the sql example above being one of them), I showed how it would work, see example at end.
 and the end result was something that seemed focused solely on writef and 
printf functions. Much more accurately, it was optimized for all functions that use either printf-style formatting strings or writef-style formatting functions. This is because those style functions are overwhelmingly used in D programs and are everywhere in C code. In another post I compared #DIP1027 and #DIP1036 side-by-side for printf and writeln. (DIP1036 also requires an overload of writefln to work with anything other than %s formatting.) Let's compare mysql: DIP1036: mysql_query(i"select * from foo where id = ${obj.id} and val > ${minval}"); DIP1027: mysql_query(i"select * from foo where id = ${?}(obj.id) and val > ${?}minval"); The DIP1027 could be shorter or longer, depending on if the ( ) were needed or not. DIP1036 also requires a user-written overload for mysql_query(), DIP1027 does not. DIP1036 is not a clear winner for mysql.
Jan 29 2021
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 29 January 2021 at 21:21:54 UTC, Walter Bright wrote:
 Much more accurately, it was optimized for all functions that 
 use either printf-style formatting strings or writef-style 
 formatting functions. This is because those style functions are 
 overwhelmingly used in D programs and are everywhere in C code.
My impression is that `writeln` is more common than `writefln` in D code, and a quick grep of Phobos supports that impression: $ grep -R 'writeln' std/ | wc -l 321 $ grep -R 'writefln' std/ | wc -l 225 Obviously not scientific, but at the very least it shows that formatted output is not the clear and unambiguous winner.
Jan 29 2021
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Jan 29, 2021 at 09:33:08PM +0000, Paul Backus via Digitalmars-d wrote:
 On Friday, 29 January 2021 at 21:21:54 UTC, Walter Bright wrote:
 Much more accurately, it was optimized for all functions that use
 either printf-style formatting strings or writef-style formatting
 functions.  This is because those style functions are overwhelmingly
 used in D programs and are everywhere in C code.
My impression is that `writeln` is more common than `writefln` in D code, and a quick grep of Phobos supports that impression: $ grep -R 'writeln' std/ | wc -l 321 $ grep -R 'writefln' std/ | wc -l 225 Obviously not scientific, but at the very least it shows that formatted output is not the clear and unambiguous winner.
My experience is on the contrary: I use writefln in my own code a lot more than writeln. Of course, that's just another data point, and may or may not represent actual usage. T -- Answer: Because it breaks the logical sequence of discussion. / Question: Why is top posting bad?
Jan 29 2021
prev sibling next sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/21 4:21 PM, Walter Bright wrote:
 In reply to Steve in the Feedback thread:
 
[snip] Forgive me, but isn't this a duplicate of https://forum.dlang.org/post/rv0grf$g1j$1 digitalmars.com ? I may be missing something new here, but I did reply to that in the feedback thread. -Steve
Jan 29 2021
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
FYI, I was just made aware that this is because the message in the 
feedback thread is about to be deleted. I'm posting my reply the same as 
it was in there.

On 1/29/21 3:26 AM, Walter Bright wrote:
  > in DIP1027, formatting was a central component of interpolation. 
What became clear as the prior version was reviewed was that the complexity of specifying format while transforming into a parameter sequence was not worth adding to the language.
 Not clear to me that ${?} is that complex, and the language itself 
did not need to know what the format was, it just had to fit inside the ${ }. ${?}(expr) is more complex than ${expr}. If that is not clear, I'm not sure how else to describe it. There are two opportunities for complexity here. The complexity of implementation and the complexity of usage. In fact, DIP1027 has a higher level of both usage complexity and implementation complexity. And it loads at least one chamber of the footgun at all times. For DIP1027, a SQL library author can possibly accept $(expr) for parameters. What this means is that a `%s` will be added to the SQL string. It is possible that the library author can ADD the complexity of parsing and detecting %s inside the SQL string, replacing them with ? tokens. This is a huge added complexity that is not necessary for just a 10 minute implementation of concatenating literal strings with ? tokens (which can be done at compile-time by the way). Not only that, but the user is allowed to use whatever they want in the {}. This means that a user can write ${bbbb}(param) in the string and it will compile. The library can't *possibly* figure this out. It can complain at runtime of an error in format, reimplementing the entire SQL language parsing client side, or potentially just use the SQL server to do it. But it also may result in incorrect *successful* calls. There is just no way to form an API that guards against user error at compile time or runtime with DIP1027. And on the user side, there is the ever-present guess if the SQL library is going to have special handling for the %s. They can add defensive format specifiers for everything, but again, this results in added complexity that is not necessary in DIP1036. In fact, DIP1036 provides the *opportunity* for malformed SQL statements to be rejected at *compile time*, something that is not available in DIP1027. Finally, DIP1036 provides a straightforward path for formatting that is simply not possible with DIP1027. This is because no parsing of specifiers is necessary at runtime. The mechanism of "parse this string to see what to do" is a relic of languages that do not provide compile-time metaprogramming capabilities, and should be avoided in a feature designed for one.
  > and the end result was something that seemed focused solely on 
writef and printf functions.
 Much more accurately, it was optimized for all functions that use 
either printf-style formatting strings or writef-style formatting functions. This is because those style functions are overwhelmingly used in D programs and are everywhere in C code. It was not optimized for anything but writef and format. Yes, I purposely omitted printf, since it's not optimized for that. You must provide format specifiers for everything but the rare-in-D const char * parameter type. For example, if you have `string name;`, how would you use printf to format that? With an actual printf call, it's: printf("hello, %.*s\n", cast(int)name.length, name.ptr); With DIP1027 it's possible to do, but you wouldn't EVER do this: printf("hello, ${%.*}(cast(int)name.length)${s}(name.ptr)\n"); With DIP1036 and a 15-minute wrapper that I wrote it's: printf("hello, ${name}\n"); For all other use cases, one must inject format specifiers for all parameters, or have a function that compiles but always does the wrong thing. One must know the format specifiers for that specific domain, and therefore places the burden of the API writing on the user at all times. Furthermore, there is NO POSSIBLE WAY for DIP1027 to allow a library author to help. Even if a library wants to provide a mechanism to allow unformatted string interpolation usage, he cannot do so.
 In another post I compared #DIP1027 and #DIP1036 side-by-side for 
printf and writeln. (DIP1036 also requires an overload of writefln to work with anything other than %s formatting.) DIP1027 does not work with writef unless you call it in a very specific way. DIP1027 cannot work with writeln, even with an overload. DIP1036 will work with writeln out of the box, and with an overload can provide an actual universally usable mechanism for formatted strings with interpolation strings. DIP1027 is basically a user rewrite of parameters that happens to work with a select few functions when called in a select few ways.
 Let's compare mysql:

 DIP1036:

 mysql_query(i"select * from foo where id = ${obj.id} and val > 
${minval}");
 DIP1027:

 mysql_query(i"select * from foo where id = ${?}(obj.id) and val > 
${?}minval");
 The DIP1027 could be shorter or longer, depending on if the ( ) were 
needed or not. DIP1036 also requires a user-written overload for mysql_query(), DIP1027 does not. DIP1036 is not a clear winner for mysql. DIP1027 will always be longer: ${?} is 4 characters. ${} is 3. DIP1036 ALLOWS an overload that works exactly as the user expects DIP1027 PREVENTS an overload that works as the user expects. DIP1036 can reject malformed parameters at compile time. DIP1027 is basically a rewrite of user parameters, and puts an immense burden on them that is not necessary with DIP1036. It takes all the power away from a library writer in helping the user use their library properly. DIP1036 is a clear winner for mysql. -Steve
Jan 29 2021
parent reply Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Saturday, 30 January 2021 at 02:57:59 UTC, Steven 
Schveighoffer wrote:
 FYI, I was just made aware that this is because the message in 
 the feedback thread is about to be deleted. I'm posting my 
 reply the same as it was in there.

 [...]
In light of the criticism from various people, is there some kind of "middle ground" solution that could be made? 🤔 Like for example printf and friends, could the DIP somehow either have some helpers and/or show clear examples on how you would use interpolation in those cases? Btw, I want this to pass, so I'm just trying to find a way forward.
Jan 31 2021
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 31 January 2021 at 12:24:30 UTC, Imperatorn wrote:
 On Saturday, 30 January 2021 at 02:57:59 UTC, Steven 
 Schveighoffer wrote:
 FYI, I was just made aware that this is because the message in 
 the feedback thread is about to be deleted. I'm posting my 
 reply the same as it was in there.

 [...]
In light of the criticism from various people, is there some kind of "middle ground" solution that could be made? 🤔
DIP 1036 *is* the middle-ground solution. That's why it's such a mess. The fact is, there's no broad consensus in the D community about how string interpolation ought to work. So no matter what solution you come up with, somebody is going to be disappointed with it.
Jan 31 2021
next sibling parent reply Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Sunday, 31 January 2021 at 12:59:27 UTC, Paul Backus wrote:
 On Sunday, 31 January 2021 at 12:24:30 UTC, Imperatorn wrote:
 On Saturday, 30 January 2021 at 02:57:59 UTC, Steven 
 Schveighoffer wrote:
 FYI, I was just made aware that this is because the message 
 in the feedback thread is about to be deleted. I'm posting my 
 reply the same as it was in there.

 [...]
In light of the criticism from various people, is there some kind of "middle ground" solution that could be made? 🤔
DIP 1036 *is* the middle-ground solution. That's why it's such a mess. The fact is, there's no broad consensus in the D community about how string interpolation ought to work. So no matter what solution you come up with, somebody is going to be disappointed with it.
If that's the case we can just proceed, because a solution with 100% approval will never exist.
Jan 31 2021
parent reply SealabJaster <sealabjaster gmail.com> writes:
On Sunday, 31 January 2021 at 13:17:21 UTC, Imperatorn wrote:
 because a solution with 100% approval will never exist.
Which is exactly why I fear string interpolation is never going to be accepted in the first place.
Jan 31 2021
parent reply Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Sunday, 31 January 2021 at 13:25:01 UTC, SealabJaster wrote:
 On Sunday, 31 January 2021 at 13:17:21 UTC, Imperatorn wrote:
 because a solution with 100% approval will never exist.
Which is exactly why I fear string interpolation is never going to be accepted in the first place.
Well, nor DIP at all tbh 😬 Maybe a voting system would make it clearer, idk.
Jan 31 2021
parent Paul Backus <snarwin gmail.com> writes:
On Sunday, 31 January 2021 at 13:41:13 UTC, Imperatorn wrote:
 On Sunday, 31 January 2021 at 13:25:01 UTC, SealabJaster wrote:
 On Sunday, 31 January 2021 at 13:17:21 UTC, Imperatorn wrote:
 because a solution with 100% approval will never exist.
Which is exactly why I fear string interpolation is never going to be accepted in the first place.
Well, nor DIP at all tbh 😬 Maybe a voting system would make it clearer, idk.
The only thing a DIP needs to get accepted is Walter and Atila's approval. Community review is not a vote or referendum; it's an opportunity for the D community to *collaborate* with the DIP authors and help them improve their proposal.
Jan 31 2021
prev sibling parent reply claptrap <clap trap.com> writes:
On Sunday, 31 January 2021 at 12:59:27 UTC, Paul Backus wrote:
 On Sunday, 31 January 2021 at 12:24:30 UTC, Imperatorn wrote:
 On Saturday, 30 January 2021 at 02:57:59 UTC, Steven 
 Schveighoffer wrote:
 FYI, I was just made aware that this is because the message 
 in the feedback thread is about to be deleted. I'm posting my 
 reply the same as it was in there.

 [...]
In light of the criticism from various people, is there some kind of "middle ground" solution that could be made? 🤔
DIP 1036 *is* the middle-ground solution. That's why it's such a mess. The fact is, there's no broad consensus in the D community about how string interpolation ought to work. So no matter what solution you come up with, somebody is going to be disappointed with it.
Try to please everybody and you end up pleasing nobody.
Jan 31 2021
parent 12345swordy <alexanderheistermann gmail.com> writes:
On Sunday, 31 January 2021 at 13:40:10 UTC, claptrap wrote:
 On Sunday, 31 January 2021 at 12:59:27 UTC, Paul Backus wrote:
 On Sunday, 31 January 2021 at 12:24:30 UTC, Imperatorn wrote:
 On Saturday, 30 January 2021 at 02:57:59 UTC, Steven 
 Schveighoffer wrote:
 FYI, I was just made aware that this is because the message 
 in the feedback thread is about to be deleted. I'm posting 
 my reply the same as it was in there.

 [...]
In light of the criticism from various people, is there some kind of "middle ground" solution that could be made? 🤔
DIP 1036 *is* the middle-ground solution. That's why it's such a mess. The fact is, there's no broad consensus in the D community about how string interpolation ought to work. So no matter what solution you come up with, somebody is going to be disappointed with it.
Try to please everybody and you end up pleasing nobody.
This right here. Sometimes you got to make a conversational decision in order for d to progress. -Alex
Jan 31 2021
prev sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/31/21 7:24 AM, Imperatorn wrote:
 On Saturday, 30 January 2021 at 02:57:59 UTC, Steven Schveighoffer wrote:
 FYI, I was just made aware that this is because the message in the 
 feedback thread is about to be deleted. I'm posting my reply the same 
 as it was in there.

 [...]
In light of the criticism from various people, is there some kind of "middle ground" solution that could be made? 🤔 Like for example printf and friends, could the DIP somehow either have some helpers and/or show clear examples on how you would use interpolation in those cases?
I've already done that, see https://run.dlang.io/is/X5HcXS printf with this DIP and it's BetterC compatible. But that is not the point of the DIP at all. We are not trying to make a fancy way to call printf. So in my opinion, this is a distraction. It just shows the power of the DIP can make what you want trivial to accomplish.
 Btw, I want this to pass, so I'm just trying to find a way forward.
I don't think it's a question of middle ground, I think it's a question of goals. -Steve
Jan 31 2021
prev sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Wednesday, 27 January 2021 at 10:33:08 UTC, Mike Parker wrote:
 This is the discussion thread for the second round of Community 
 Review of DIP 1036, "String Interpolation Tuple Literals":
The whole "${}" thing is too verbose, it reminds me of the JavaScript variant which is basically no easier to read than concatenation. Python got it mostly right... But I think D should reject superfluous features like this that has very limited benefits. The compiler should be made simpler, and deal with things like memory and type system before more code is added.
Feb 04 2021
next sibling parent reply Luhrel <lucien.perregaux gmail.com> writes:
On Thursday, 4 February 2021 at 08:10:57 UTC, Ola Fosheim Grøstad 
wrote:
 On Wednesday, 27 January 2021 at 10:33:08 UTC, Mike Parker 
 wrote:
 This is the discussion thread for the second round of 
 Community Review of DIP 1036, "String Interpolation Tuple 
 Literals":
The whole "${}" thing is too verbose, it reminds me of the JavaScript variant which is basically no easier to read than concatenation. Python got it mostly right... But I think D should reject superfluous features like this that has very limited benefits. The compiler should be made simpler, and deal with things like memory and type system before more code is added.
In Python, there is just the dollar sign less... -- The DIP does not mention anything about wstring and dstring. ``` double price = 1.05; auto a = i"Une baguette coûte ${price}€"w; ``` Should works too.
Feb 04 2021
next sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 4 February 2021 at 09:22:38 UTC, Luhrel wrote:
 The DIP does not mention anything about wstring and dstring.
That's what StringPosfix is in the grammar: +InterpolatedDoubleQuotedString: + i" InterpolatedDoubleQuotedCharacters " StringPostfix[opt] From the text: "the result of the idup function will be a string, wstring, or dstring depending on the type of the literal." Already in my poc too: https://github.com/adamdruppe/dmd/tree/string_interp
Feb 04 2021
prev sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Thursday, 4 February 2021 at 09:22:38 UTC, Luhrel wrote:
 In Python, there is just the dollar sign less...
Yes, and that makes a big difference in terms of visual clutter. It also provides string formatting that is close to printf. f'{year}-{month:02}-{day:02}' Very little visual clutter. If it looks worse than this, then I won't use it...
Feb 04 2021
prev sibling next sibling parent reply ddcovery <antoniocabreraperez gmail.com> writes:
On Thursday, 4 February 2021 at 08:10:57 UTC, Ola Fosheim Grøstad 
wrote:

 The whole "${}" thing is too verbose, it reminds me of the 
 JavaScript variant which is basically no easier to read than 
 concatenation.  Python got it mostly right...
In my opinion, `${a} plus ${b} is ${a+b}` is definitely easier to read than "" + a + " plus " + b + " is " + (a+b) Dart "${a} plus ${b} is ${a+b}" or "$a plus $b is ${a+b}" Kotlin "${a} plus ${b} is ${a+b}" or "$a plus $b is ${a+b}" Javascript/Typescript `${a} plus ${b} is ${a+b}` Python template = Template('$a plus $b is $c) print( template.substitute(a=a, b=b, c=a+b) ); or print( Template('$a plus $b is $c).substitute(a=a, b=b, c=a+b) ); That without taking into account that python solution is a "run time" template compiler while Dart/typescript/... is a compile time type safe in place string expression.
Feb 04 2021
next sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Thursday, 4 February 2021 at 10:38:00 UTC, ddcovery wrote:
 On Thursday, 4 February 2021 at 08:10:57 UTC, Ola Fosheim 
 Grøstad wrote:

 The whole "${}" thing is too verbose, it reminds me of the 
 JavaScript variant which is basically no easier to read than 
 concatenation.  Python got it mostly right...
In my opinion, `${a} plus ${b} is ${a+b}` is definitely easier to read than "" + a + " plus " + b + " is " + (a+b) Dart "${a} plus ${b} is ${a+b}" or "$a plus $b is ${a+b}" Kotlin "${a} plus ${b} is ${a+b}" or "$a plus $b is ${a+b}" Javascript/Typescript `${a} plus ${b} is ${a+b}`
Python: f'{a} plus {b} is {a+b}'
Feb 04 2021
parent ddcovery <antoniocabreraperez gmail.com> writes:
On Thursday, 4 February 2021 at 15:24:56 UTC, Ola Fosheim Grøstad 
wrote:
 Python:

 f'{a} plus {b} is {a+b}'
Thanks for updating me :-)
Feb 05 2021
prev sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Thursday, 4 February 2021 at 10:38:00 UTC, ddcovery wrote:
 In my opinion,

   `${a} plus ${b} is ${a+b}`

 is definitely easier to read than

   "" + a + " plus " + b + " is " + (a+b)
I don't think it is a big difference, of course, JavaScript also converts to string automagically, which makes string interpolation much less useful. Often it is sufficient to just do: [a, 'plus', b, 'is', a+b].join(' ')
Feb 04 2021
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 4 February 2021 at 15:39:53 UTC, Ola Fosheim Grøstad 
wrote:
 of course, JavaScript also converts to string automagically
if and only if you use the default function. foo`string` in javascript does not necessary convert to string.
Feb 04 2021
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Thursday, 4 February 2021 at 15:48:47 UTC, Adam D. Ruppe wrote:
 On Thursday, 4 February 2021 at 15:39:53 UTC, Ola Fosheim 
 Grøstad wrote:
 of course, JavaScript also converts to string automagically
if and only if you use the default function. foo`string` in javascript does not necessary convert to string.
Yes, I like the meta-programming aspect, but I think it can be solved by overloading so that foo can be used for other symbols. Does the proposal allow for this? For instance you could require that templated function foo__string__interpolation__(…)(…){…} will only be called if it prefixes foo`string` Then you could have very short symbols as prefixes without wasting precious symbols.
Feb 04 2021
prev sibling parent reply claptrap <clap trap.com> writes:
On Thursday, 4 February 2021 at 08:10:57 UTC, Ola Fosheim Grøstad 
wrote:
 On Wednesday, 27 January 2021 at 10:33:08 UTC, Mike Parker 
 wrote:
 This is the discussion thread for the second round of 
 Community Review of DIP 1036, "String Interpolation Tuple 
 Literals":
The whole "${}" thing is too verbose, it reminds me of the JavaScript variant which is basically no easier to read than concatenation. Python got it mostly right...
Isn't the {} thing so you can do.. i"i ate ${apples + bananas} of my 5 a day" Maybe it could allow i"i ate $apples apples today" too?
Feb 04 2021
next sibling parent reply Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Thursday, 4 February 2021 at 11:00:24 UTC, claptrap wrote:
 On Thursday, 4 February 2021 at 08:10:57 UTC, Ola Fosheim 
 Grøstad wrote:
 On Wednesday, 27 January 2021 at 10:33:08 UTC, Mike Parker 
 wrote:
 This is the discussion thread for the second round of 
 Community Review of DIP 1036, "String Interpolation Tuple 
 Literals":
The whole "${}" thing is too verbose, it reminds me of the JavaScript variant which is basically no easier to read than concatenation. Python got it mostly right...
Isn't the {} thing so you can do.. i"i ate ${apples + bananas} of my 5 a day" Maybe it could allow i"i ate $apples apples today" too?
i"I ate {nrOfApples} apples today" and i"I ate {nrOfApples + nrOfOranges} fruits today" Clean imo
Feb 04 2021
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 4 February 2021 at 12:27:49 UTC, Imperatorn wrote:


 i"I ate {nrOfApples} apples today" and
 i"I ate {nrOfApples + nrOfOranges} fruits today"

 Clean imo
Note that we chose the sequence ${expr} to avoid duplicating standard uses of symbols inside strings. This next bit isn't in the dip but Just using {} alone would conflict with tons of D syntax. mixin(iq{ void foo() { is this a function body or an interpolated section? } });
Feb 04 2021
next sibling parent reply Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Thursday, 4 February 2021 at 12:56:14 UTC, Adam D. Ruppe wrote:
 On Thursday, 4 February 2021 at 12:27:49 UTC, Imperatorn wrote:


 i"I ate {nrOfApples} apples today" and
 i"I ate {nrOfApples + nrOfOranges} fruits today"

 Clean imo
Note that we chose the sequence ${expr} to avoid duplicating standard uses of symbols inside strings. This next bit isn't in the dip but Just using {} alone would conflict with tons of D syntax. mixin(iq{ void foo() { is this a function body or an interpolated section? } });
That would be like an interpolated verbatim string. "iq" would be roughly equal to "$ " in that case. In the example you provided, it depends on what character is used. If { and } are used (instead of $), you need to escape those by { and }. are present, also, since 8.0 the order doesn't matter, ie $ EQU $ ): 1. string s = $"{ void foo() { is this a function body or an interpolated section? } }"; 2. string s = $"{{ void foo() { is this a function body or an interpolated section? } }}"; In case 1 the entire expression is evaluated, in case 2 the beginning and end curly brackets are "escaped" and therefore the inner expression is evaluated. More information can be found here: https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/string-interpolation https://docs.microsoft.com/en-us/dotnet/standard/base-types/composite-formatting
Feb 04 2021
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 4 February 2021 at 13:37:32 UTC, Imperatorn wrote:
 In the example you provided, it depends on what character is 
 used. If { and } are used (instead of $), you need to escape 
 those by { and }.
Don't you see how that would make the string uglier and harder to use correctly by forcing escaping of an extremely common sequence? Remember, this is used with D code, along side curly brace syntax and the $ operator in slices, as well as for ordinary strings that use $ to mean a bunch of other things. ${} is unambiguous in token strings and much more clear in normal strings.
Feb 04 2021
parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Thursday, 4 February 2021 at 13:49:30 UTC, Adam D. Ruppe wrote:
 On Thursday, 4 February 2021 at 13:37:32 UTC, Imperatorn wrote:
 In the example you provided, it depends on what character is 
 used. If { and } are used (instead of $), you need to escape 
 those by { and }.
Don't you see how that would make the string uglier and harder to use correctly by forcing escaping of an extremely common sequence? Remember, this is used with D code, along side curly brace syntax and the $ operator in slices, as well as for ordinary strings that use $ to mean a bunch of other things. ${} is unambiguous in token strings and much more clear in normal strings.
reference.
Feb 04 2021
prev sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Thursday, 4 February 2021 at 12:56:14 UTC, Adam D. Ruppe wrote:
 This next bit isn't in the dip but Just using {} alone would 
 conflict with tons of D syntax.
No? Even a lexer can disambiguate? Just count pairs of braces. You know that the opening brace is interpolating, so then you know what the closing one is.
Feb 04 2021
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 4 February 2021 at 15:29:47 UTC, Ola Fosheim Grøstad 
wrote:
 You know that the opening brace is interpolating, so then you 
 know what the closing one is.
but you don't know that the opening brace is interpolating. enum name = "foo"; mixin(iq{ void {name}() {bar} }); Is {bar} interpolation or a function body? With ${} there's no question. mixin(iq{ void ${name}() {bar} }); name is interpolated, bar is a body.
Feb 04 2021
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Thursday, 4 February 2021 at 15:34:07 UTC, Adam D. Ruppe wrote:
 but you don't know that the opening brace is interpolating.

 enum name = "foo";
 mixin(iq{ void {name}() {bar} });


 Is {bar} interpolation or a function body?
It would be interpolation. If you are in text (string) mode when hitting "{" it would be taken as opening an expression that should be converted to string. So, no ambiguity.
Feb 04 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Thursday, 4 February 2021 at 17:00:43 UTC, Ola Fosheim Grøstad 
wrote:
 On Thursday, 4 February 2021 at 15:34:07 UTC, Adam D. Ruppe 
 wrote:
 but you don't know that the opening brace is interpolating.

 enum name = "foo";
 mixin(iq{ void {name}() {bar} });


 Is {bar} interpolation or a function body?
It would be interpolation. If you are in text (string) mode when hitting "{" it would be taken as opening an expression that should be converted to string. So, no ambiguity.
This makes using interpolation to generate D code significantly more cumbersome, because '{' is a common character in D code, and every occurrence of it would have to be escaped. It is much easier if the sequence used to introduce an interpolated expression is not a valid D token.
Feb 04 2021
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Thursday, 4 February 2021 at 17:05:30 UTC, Paul Backus wrote:
 This makes using interpolation to generate D code significantly 
 more cumbersome, because '{' is a common character in D code, 
 and every occurrence of it would have to be escaped. It is much 
 easier if the sequence used to introduce an interpolated 
 expression is not a valid D token.
Good! String mixins are evil, just a small step away from C-macros... Their use should be discouraged and limited to the cases where the language falls short.
Feb 04 2021
parent 12345swordy <alexanderheistermann gmail.com> writes:
On Thursday, 4 February 2021 at 20:46:15 UTC, Ola Fosheim Grøstad 
wrote:
 On Thursday, 4 February 2021 at 17:05:30 UTC, Paul Backus wrote:
 This makes using interpolation to generate D code 
 significantly more cumbersome, because '{' is a common 
 character in D code, and every occurrence of it would have to 
 be escaped. It is much easier if the sequence used to 
 introduce an interpolated expression is not a valid D token.
Good! String mixins are evil, just a small step away from C-macros... Their use should be discouraged and limited to the cases where the language falls short.
They are no where close to c-macros. What are you even talking about? -Alex
Feb 04 2021
prev sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 4 February 2021 at 11:00:24 UTC, claptrap wrote:
 i"i ate $apples apples today"
Note that we chose the sequence ${expr} to avoid duplicating standard uses of symbols inside strings (using just '$' alone would trigger a lot of unintentional usage, considering its use as a denomination specifier, or as a substitute for array length for mixin code)
Feb 04 2021