February 05, 2020
On 2/4/20 6:03 PM, Adam D. Ruppe wrote:
> On Tuesday, 4 February 2020 at 18:41:20 UTC, jmh530 wrote:
>> Under the addendum proposal, is it also the case that functions that take strings or printf can instead take the interpolated string?
> 
> That would depend on the druntime implementation. I think my preference would be it implicitly casts to string and const char* if and only if all format specifiers are given by the user.
> 
> I just wrote up a revised DIP based on Walter's to detail my current thoughts:
> 
> https://gist.github.com/adamdruppe/a58f097d974b364ae1cbc8c050dd9a3f
> 

Thanks for writing this up.

A few things:

0. You have missed one edit:

* `'$' '{' FormatString '}' Argument`, then `_d_interpolated_format_spec(FormatString)` is written to the output string.

Should say "is appended to the argument list"

1. I hate that this doesn't work:

alias toFormatString!null this

I did some investigation. The compiler treats enum'd strings as convertible to const char * in many cases but not all. For example:

enum str = "hi\n";
enum str2 = "hello " ~ "world!\n";
enum str3 = text("hello", " world!\n");

printf(str); // ok
printf(str2); // ok
printf(str3); // nope

IMO, this is a bug, no reason not to make this consistent.

So I thought I would make a fancy mixin that generates the enum string from a tuple of strings using only concatenation. It works!

But then I ran into another problem -- namely that alias this somehow removes this ability to auto-convert to const char *.

This seems like another bug to me, and a limitation that we should fix.

BTW, I really like that you give the library writer the ability to alter the default spec so easily. Writing small shims should be trivial.

2. I disagree that you shouldn't be able to do writefln(i"someint : $someint") without adding an overload to writefln. That is the one benefit of Walter's proposal that I like -- you can use it out of the box with existing functions.

I know it means you have to be aware of the problems that can occur, and understand how the lowering actually works. But there are going to be quite a few functions that exist already with this kind of mechanism, and I don't want to make all those functions inaccessible except via adding overloads.

But your CreateWindow example is also pretty compelling. It would be nice to make "send the first parameter as a string" opt-in. Is there a way we can do that without requiring overloads?

3. I'm also not sold on the "if they provided a format specifier, they know it returns a tuple" logic. Plenty of existing string interpolation libraries provide ways to format the parameters into strings, and still can be used as strings.

Note that C# does something pretty cool [1] (as I just looked it up to see what other interpolation libraries do): if the interpolation is intended to be used as a string, it lowers to a call to String.Format. If its type is going to be IFormattable or FormattableString, it generates a structure not dissimilar to what we are wanting here [2]. So there is kind of a precedent for this feature in other string interpolation implementations.

I'm not sure whether we can provide this level of seamlessness without return type overloads, but I like the idup idea, and I still think we shouldn't call this feature string interpolation, due to the existing expectation for "string interpolation". Inspired by that C# type, maybe "formattable tuple" is a good description.

-Steve

[1] https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated#compilation-of-interpolated-strings
[2] https://docs.microsoft.com/en-us/dotnet/api/system.formattablestring?view=netframework-4.8
February 06, 2020
On 06/02/2020 4:00 AM, Adam D. Ruppe wrote:
> 1) sql(i""); just works, with correct parameterization
> 2) jsx(i""); just works, with correct contextual encoding
> 3) writeln(i""); just works
> 4) printf(i""); can just work
> 5) Even readf(i""); just works!
> 6) Error detection is possible - at compile time - for all those scenarios
> 7) @nogc i"".toBuffer(...) is possible.
> 8) gettext() style internationalization is possible, including with the D compiler itself collecting the strings!
> 9) foo!i"" works to collect D aliases so like `my_debug!i"$a and $b"` might give
>    a = 5 and b = 3
> 10) whatever else library authors can dream up!

Oh that is looking good.

Now that is something I could use.
February 05, 2020
On 2/5/20 5:46 AM, Paolo Invernizzi wrote:
> it will be my first DIP vote, and it will be against it.

I didn't think DIPs got voted on, I thought just W&A made a decision and that was it.

Not that the community can't give feedback or affect those decisions, but there isn't a "voting" phase here: https://github.com/dlang/DIPs/blob/master/docs/process-authoring.md.

-Steve
February 05, 2020
On Wednesday, 5 February 2020 at 09:11:20 UTC, Walter Bright wrote:
> On 2/4/2020 9:48 PM, FeepingCreature wrote:
>> This may sound trivial, but trivial syntax enhancements matter, especially for a "top-ten" feature like format strings.
>
> It's a fair point, but there's a cost to supporting this. Nothing comes for free, and supporting everything is not practical.

I'd like to add that one of the reasons I utilize format strings is because of this switch. The main issue with

foo("I want ", i, "bananas");

It is a lot harder to catch the spacing around 'i'. So rather than fix the space I also reorder everything to us format syntax.

I'm on the side of leaning on D's power over C printf. I'd like to hear cost over the proposal too, maybe I missed it somewhere else?
February 05, 2020
On 2/5/20 12:26 AM, Walter Bright wrote:
> On 2/4/2020 1:59 PM, Steven Schveighoffer wrote:
>> On 2/4/20 2:12 AM, Walter Bright wrote:
>>> On 2/3/2020 5:06 PM, Jonathan Marler wrote:
>>>> Some applications want the "Strings and Expressions" form rather than the "Format String" form.
>>>
>>> To have "strings and expressions" then write it that way:
>>>
>>>      foo("I want ", i, " bananas");
>>
>> To have "formatted string output", just write it that way:
>>
>> writefln("I have %s apples and %d bananas", apples, bananas);
> 
> The reason for formatted strings is for formatting that is other than the default, and to interface with common functions that use formatted strings.

In C, the reason for formatting string is because you don't know the types of what's passed in! There is no "default".

We have adopted that mechanism in D because another nice feature is to be able to read the format of what you expect the result to be without all the comma-quote noise. But it's totally not necessary for formatting...

> 
> Strings and expressions are stuck with the default format. If you're happy with the default formatting,
> 
>      writefln("I want $i bananas");
> 
> is not much of any improvement over:
> 
>      writeln("I want ",i," bananas");
> 

They aren't stuck:

writeln("I want ", someFloat.fmt("%0.2f"), " bananas");

We don't even need to change writeln to make this work today. But who would rather write that than a string interpolation tuple?

The point of my original (admittedly snarky, sorry) reply to your rebuttal here is that your logic can be used to completely dismiss the whole string interpolation proposal. Why make the compiler more complicated when you can just "write it that way"?

The answer is: because the other way is easier to write, easier to maintain, easier to read and overall improves the lives of developers everywhere. Syntax sugar is never necessary, but when it makes a difference it's a huge factor in the way it feels to use a language. I don't think we should dismiss features just because they can be written a different way in the existing language.

-Steve
February 05, 2020
On Wednesday, 5 February 2020 at 15:00:31 UTC, Adam D. Ruppe wrote:
> 1) sql(i""); just works, with correct parameterization
> 2) jsx(i""); just works, with correct contextual encoding
> 3) writeln(i""); just works
> 4) printf(i""); can just work
> 5) Even readf(i""); just works!
> 6) Error detection is possible - at compile time - for all those scenarios
> 7) @nogc i"".toBuffer(...) is possible.
> 8) gettext() style internationalization is possible, including with the D compiler itself collecting the strings!
> 9) foo!i"" works to collect D aliases so like `my_debug!i"$a and $b"` might give
>   a = 5 and b = 3
> 10) whatever else library authors can dream up!
>
> All this while keeping it type-safe to detect improper or even just inefficient uses.
>
> And, of course, `string s = i"".idup;` is still a very convenient option when you want it, but with so many functions just working, you may want to use them instead of string assignment!
>
> Note that Javascript's template literals allow much of this stuff too and are really cool.
>
>
> I'm chatting with a user on IRC now who feels this is a dealbreaker to him, it MUST implicitly convert. But the next best proposal for implicit conversion loses benefits #4 and #5 there (which I argued earlier in this thread was worth it!), makes #6 still possible but less generally applicable, and makes #7 and #9 pretty iffy. So I think `.idup` is an OK trade off here.
>
> Like Steven, I encourage you all to look at i"" not being an interpolated string per se, but being a "string builder literal" or something like that - it is syntax sugar that gives an entity you can get a string out of rather than a string itself. An interpolated string is just one of the *many* things you can build with this new literal, and I expect that once you use it with some cool library support, you'll agree the explicit `idup` in some cases - and that's all you have to do to get the plain GC string out of it, no import necessary for that case in my proposal - is worth the gains we get in all these other cases too.

A better feature would be if you could just create your own function then. Cause having to put %d/f/etc... when you want to use printf is just backwards. C++ has a feature that lets users process strings, and they can customize how it gets interpreted. We can do something similar.

> 1) sql(i""); just works, with correct parameterization
> 2) jsx(i""); just works, with correct contextual encoding
> 3) writeln(i""); just works
> 4) printf(i""); can just work
> 5) Even readf(i""); just works!

These don't "just work". You have to manually add the specifier based on what you are calling.

If you can define your own interpolated string implementation, then it can.

    float value;
    string table;

    // current proposal, doesn't "just work":
    sql(i"SELECT * FROM $table WHERE value = $value");
    sql("SELECT * FROM %s where value = %s", table, value); // error

    // now it works, but not "just":
    sql(i"SELECT * FROM ${?}table WHERE value = ${?}value");
    sql("SELECT * FROM ? WHERE value = ?", table, value);

    // just works:
    sql(sql_i"SELECT * FROM $table WHERE value = $value");
    sql("SELECT * FROM ? WHERE value = ?", table, value);

    // current proposal, doesn't "just work":
    printf(i"The $table and $value");
    printf("The %s and %s", table, value); // error + unsafe

    // now it works, but not "just":
    printf(i"The $table and ${%d}value");
    printf("The %s and %d", table, value); // ops, still "works"

    // ok now for real it works, but not "just"
    printf(i"The $table and ${%f}value");
    printf("The %s and %f", table, value);

    // just works:
    printf(fmt_i"The $table and $value");
    printf("The %s and %f", table, value);

Otherwise if you have to manually specify what specifiers to use, then this is dead on arrival.

-1 my vote for the proposal if you have to manual put specifiers, someone should put up a poll.




February 05, 2020
On Wednesday, 5 February 2020 at 15:20:56 UTC, Steven Schveighoffer wrote:
> 0. You have missed one edit:

thx, I'll come back to it later.

> 1. I hate that this doesn't work:
>
> alias toFormatString!null this

Yeah, I assumed it would because an eponymous template to an enum string does work. But then you lose the ability to detect the new type (maybe if we had typedef LOL).

But it doesn't... and you know that createWindow one actually really gets to me. I think that is likely to be a big problem with unintentional interactions confusing people. And, of course, other people's concerns about implicit GC contextual allocations are fair too.

However, I agree it is likely a bug. That said... this is one of those trade-offs that could go either way anyway.

Of course, it is purely in the library which in theory can be tweaked more easily in the future.


> 2. I disagree that you shouldn't be able to do writefln(i"someint : $someint")
>
> But your CreateWindow example is also pretty compelling.

Yeah, this is back to the both-sides-have-merit trade-off. I could probably go either way - we could default to permissive and handle more cleanly with another lib overload too (which we also talked about earlier in this thread). Or restrictive with helper functions as I'll get into next.

> It would be nice to make "send the first parameter as a string" opt-in. Is there a way we can do that without requiring overloads?

I can't think of a perfect way. Can't use a helper template on params (call(help!i"xx")) since that triggers "variable X is not available at compile time", and can't do a helper UFCS function (call(i"xx").help) since they cannot return a tuple.

We could do `help!call(i"..")` though... opt-in at the call site on the function, not the args. (I'm using "help" cuz i can't think of a name right now but meh). So basically a template that makes a new overload right there.

callWithFirstAsString!writefln(i"...");

and the implementation is

auto help(alias fn, T...)(T t) {
    return fn(t[0].toFormatString!"%s", t[1 .. $]);
}

so that's easy enough. And of course you could provide other customization arguments in there like a different default formatter etc. So that could be our hook into existing functions.


And of course that could be done for printf too instead of alias const char* this, but then we're talking an import so... again not perfect but plenty of good enough library options we can talk about or even develop independently once we get the core language rewrite in place.

Interestingly a printf one could inspeect the types of t[1..$] to change the default specifier, e.g. if(is(typeof(t) == int) fmt ~= "%d" else static assert(0) and so on. So it could actually work quite nicely. Of course do that as a ctfe to enum so it isn't GC at runtime. (or failing that use a stack buffer.)

It just needs that help!printf instead of printf. Which we can debate. (I actually would prefer to go that way but I'm trying to maintain Walter's printf desire too.)

But lots of options. With zero changes to dmd itself once we get this change.

> 3. I'm also not sold on the "if they provided a format specifier, they know it returns a tuple" logic. Plenty of existing string interpolation libraries provide ways to format the parameters into strings, and still can be used as strings.

Yeah, I just figure if the user is specifying all the strings it means they must have at least thought about the format of those specifiers. They might be doing it for other purposes or just be wrong, but eh.

Note the only change that did is enable `alias this` conversions; you can still ignore it and use the other facilities.


> Note that C# does something pretty cool [1] (as I just looked it up to see what other interpolation libraries do)

Indeed, not bad. It looks like C# is similar to my old proposal, creating an object :) Then we could implicitly convert to a single string with `alias toString this` or use the specialized type. but of course i think this tuple is a wee bit better since it lets us keep `ref` and `alias`. Might also be relevant with other storage classes like `scope`, `return`, etc. So that's the trade off there (with current D at least).

I could go either way, but like I said in my other message with the list, I think the tuple's potential is indeed worth sacrificing the object's convenience (but idup and other functions 95% make up for that anyway!).

> Inspired by that C# type, maybe "formattable tuple" is a good description.

that would work.
February 05, 2020
On Wednesday, 5 February 2020 at 16:02:03 UTC, Arine wrote:
>     // current proposal, doesn't "just work":
>     sql(i"SELECT * FROM $table WHERE value = $value");
>     sql("SELECT * FROM %s where value = %s", table, value); // error

That's Walter's proposal. With *my* proposal, the decision for %s or ? or whatever is made by the library function (which is passed the knowledge it needs to know to make a smart decision) instead of by the language. So, for the end user, those things DO just work.

Remember: I am NOT defending DIP 1027 as-is. I am defending our amendment to it/rewrite of it.

See:
https://gist.github.com/adamdruppe/a58f097d974b364ae1cbc8c050dd9a3f
February 05, 2020
On 2020-02-05 16:20, Steven Schveighoffer wrote:

> Note that C# does something pretty cool [1] (as I just looked it up to see what other interpolation libraries do): if the interpolation is intended to be used as a string, it lowers to a call to String.Format. If its type is going to be IFormattable or FormattableString, it generates a structure not dissimilar to what we are wanting here [2]. So there is kind of a precedent for this feature in other string interpolation implementations.
> 
> I'm not sure whether we can provide this level of seamlessness without return type overloads, but I like the idup idea

I've been thinking something similar for quite a while. Here's my idea, it's a simple extension to Adam's proposal:

* If the string interpolation expression appears in the code as an argument to a function and if that function is declared with the @stringInterpolation UDA it generates the struct according to Adam's proposal. Example:

@stringInterpolation string sql(T)(T) { ... }
@stringInterpolation string jsx(T)(T) { ... }

float value;
string table;

sql(i"SELECT * FROM $table WHERE value = $value");

Lowered to:

sql(_d_interpolated_string!(...)(...));

* In all other cases the compiler generates the struct according to Adam's proposal BUT also calls the `idup` helper function:

auto a = "bar"
auto b = 3;
auto str = i"foo $a $b";

Lowered to:

auto str = _d_interpolated_string!(...)(...).idup;

static assert(is(typeof(str) == string));
assert(str == "foo bar 3");

I think this will be the most common usage, to just convert to a string like `std.format.format` would do.

, and I still think we
> shouldn't call this feature string interpolation, due to the existing expectation for "string interpolation". Inspired by that C# type, maybe "formattable tuple" is a good description.

Some version of "string build"? I.e. "string builder", "string building" or similar.

-- 
/Jacob Carlborg
February 05, 2020
On Wednesday, 5 February 2020 at 16:37:52 UTC, Jacob Carlborg wrote:
> I've been thinking something similar for quite a while. Here's my idea, it's a simple extension to Adam's proposal:

Anything with an @attribute makes me "bleh". I'd really rather just have the explicit function to build a string. The explicit function is not a big hassle while being more friendly to several other uses.

I'm not convinced that assigning to a string variable will actually be the most common usage though. You're surely creating this string to do something... and the variable doesn't actually do anything. You're surely going to pass it to a function eventually.. and often sooner rather than later.

I use `writefln` more often than `format`, for example. Most my cases of `"x" ~ to!string(y)` are in order to prepare a call to a function (actually most of those in my code are actually to make the `new Exception` argument - which I believe is bad code anyway, I'm just lazy af. I'd prolly just .idup an interpolated string there).

I could be wrong... but I doubt it. And the `.idup` makes it so simple even if it is common I doubt it will be a problem in practice. If we put out a beta and it was a big problem though we could revisit.

> Some version of "string build"? I.e. "string builder", "string building" or similar.

Yeah, I am thinking the formattable tuple name or maybe "string builder literal" too.

did a few more minor tweaks here including the new name btw https://gist.github.com/adamdruppe/a58f097d974b364ae1cbc8c050dd9a3f