January 29, 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
January 29, 2021
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.
January 29, 2021
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
January 29, 2021
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"))
January 29, 2021
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
January 29, 2021
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
January 29, 2021
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)));
January 29, 2021
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
January 29, 2021
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")
January 29, 2021
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-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")

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