Discussion Thread: DIP 1036--String Interpolation Tuple Literals--Community Review Round 2

Q. Schroll qs.il.paperinik at gmail.com
Thu Feb 4 18:14:59 UTC 2021

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 
The first via execute(interp!"SELECT "(), name, interp!" FROM 
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.

More information about the Digitalmars-d mailing list