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

Steven Schveighoffer schveiguy at gmail.com
Wed Feb 3 23:00:54 UTC 2021

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.


More information about the Digitalmars-d mailing list