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

Steven Schveighoffer schveiguy at gmail.com
Fri Jan 29 19:10:55 UTC 2021


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


More information about the Digitalmars-d mailing list