Thread overview
string interpolation fun
Dec 13, 2018
Jonathan Marler
Dec 13, 2018
Jonathan Marler
Dec 13, 2018
Paul Backus
Dec 13, 2018
David Gileadi
December 13, 2018
Another idea I had for solving a different problem with string interpolation: better named-field struct initialization.

The below compiles and runs as expected with https://github.com/dlang/dmd/pull/7988

//
// build T with parameters using string interpolation
//
T make(T, Args...)() if (Args.length > 0 && Args.length % 2 == 0)
{
    T result;
    import std.string;
    static foreach(i; 0 .. Args.length / 2)
    {{
        // remove all punctuation and spaces
         static assert(is(typeof(Args[i * 2]) == string));
        enum symbol = Args[i * 2].strip(" :=,");
        static if(symbol.length > 0)
        {
            __traits(getMember, result, symbol) = Args[i * 2 + 1];
        }
        else
        {
            __traits(getMember, result, __traits(identifier, Args[i * 2 + 1])) = Args[i * 2 + 1];
        }
    }}
    return result;
}

struct S
{
    int x;
    int y;
}
void main()
{
    import std.stdio;
    int x = 1;
    int y = 2;
    auto s = make!(S, i"x: $(y), y: $(x)"); // name the fields to assign
    auto s2 = make!(S, i":$(x), :$(y)"); // use symbol name as field name
    auto s3 = make!(S, i"x: $(6), y: $(7)"); // use literals to assign
    writeln(i"$(s), $(s2), $(s3)"); // runtime parameters
}


One thing we are missing here, is a sure-fire way to know which parts of the CT-sequence are literal, and which ones are parameters. For instance, at first I tried this for s2:

make!(S, i"$(x), $(y)");

but it failed because there is no prefix string for x. I couldn't think of a good static if condition that checked to see if the parameter was string literal or not, without also capturing string expressions inside the $().

Any ideas?

-Steve
December 13, 2018
On 12/13/18 10:53 AM, Steven Schveighoffer wrote:
> void main()
> {
>      import std.stdio;
>      int x = 1;
>      int y = 2;
>      auto s = make!(S, i"x: $(y), y: $(x)"); // name the fields to assign
>      auto s2 = make!(S, i":$(x), :$(y)"); // use symbol name as field name
>      auto s3 = make!(S, i"x: $(6), y: $(7)"); // use literals to assign
>      writeln(i"$(s), $(s2), $(s3)"); // runtime parameters
> }
> 
> 

Also, this would be a lot less noisy if we didn't need the parens after the $, but the PR doesn't support that (yet?).

-Steve
December 13, 2018
On Thursday, 13 December 2018 at 16:01:52 UTC, Steven Schveighoffer wrote:
> On 12/13/18 10:53 AM, Steven Schveighoffer wrote:
>> void main()
>> {
>>      import std.stdio;
>>      int x = 1;
>>      int y = 2;
>>      auto s = make!(S, i"x: $(y), y: $(x)"); // name the fields to assign
>>      auto s2 = make!(S, i":$(x), :$(y)"); // use symbol name as field name
>>      auto s3 = make!(S, i"x: $(6), y: $(7)"); // use literals to assign
>>      writeln(i"$(s), $(s2), $(s3)"); // runtime parameters
>> }
>> 
>> 
>
> Also, this would be a lot less noisy if we didn't need the parens after the $, but the PR doesn't support that (yet?).
>
> -Steve

Very cool use case.

Adding support for the `$foo` case (no parens) would be fairly trival, but requires making a decision of what type of grammar node/token to use.  We could use the same mechanism that templates use, but that would mean that something like $foo.max would be $(foo).max instead of $(foo.max) which seems a bit unexpected.  That would be a good thing include in the DIP, a list of the obvious tokens/grammar nodes we could use and their pros/cons.
December 13, 2018
On 12/13/18 11:21 AM, Jonathan Marler wrote:

> Adding support for the `$foo` case (no parens) would be fairly trival, but requires making a decision of what type of grammar node/token to use.  We could use the same mechanism that templates use, but that would mean that something like $foo.max would be $(foo).max instead of $(foo.max) which seems a bit unexpected.  That would be a good thing include in the DIP, a list of the obvious tokens/grammar nodes we could use and their pros/cons.

Yeah, I would recommend $foo.max be $(foo.max). This isn't like templates, where the dot is meaningful both ways.

-Steve
December 13, 2018
On 12/13/18 11:29 AM, Steven Schveighoffer wrote:
> On 12/13/18 11:21 AM, Jonathan Marler wrote:
> 
>> Adding support for the `$foo` case (no parens) would be fairly trival, but requires making a decision of what type of grammar node/token to use.  We could use the same mechanism that templates use, but that would mean that something like $foo.max would be $(foo).max instead of $(foo.max) which seems a bit unexpected.  That would be a good thing include in the DIP, a list of the obvious tokens/grammar nodes we could use and their pros/cons.
> 
> Yeah, I would recommend $foo.max be $(foo.max). This isn't like templates, where the dot is meaningful both ways.

I'd say it's more like the address operator:

&foo.member means &(foo.member), not &(foo).member

-Steve
December 13, 2018
On Thursday, 13 December 2018 at 16:21:48 UTC, Jonathan Marler wrote:
> On Thursday, 13 December 2018 at 16:01:52 UTC, Steven Schveighoffer wrote:
>> On 12/13/18 10:53 AM, Steven Schveighoffer wrote:
>>> void main()
>>> {
>>>      import std.stdio;
>>>      int x = 1;
>>>      int y = 2;
>>>      auto s = make!(S, i"x: $(y), y: $(x)"); // name the fields to assign
>>>      auto s2 = make!(S, i":$(x), :$(y)"); // use symbol name as field name
>>>      auto s3 = make!(S, i"x: $(6), y: $(7)"); // use literals to assign
>>>      writeln(i"$(s), $(s2), $(s3)"); // runtime parameters
>>> }
>>> 
>>> 
>>
>> Also, this would be a lot less noisy if we didn't need the parens after the $, but the PR doesn't support that (yet?).
>>
>> -Steve
>
> Very cool use case.
>
> Adding support for the `$foo` case (no parens) would be fairly trival, but requires making a decision of what type of grammar node/token to use.  We could use the same mechanism that templates use, but that would mean that something like $foo.max would be $(foo).max instead of $(foo.max) which seems a bit unexpected.  That would be a good thing include in the DIP, a list of the obvious tokens/grammar nodes we could use and their pros/cons.

I should add, the strategy I used in the PR was to establish the "foundation" of interpolated strings and postpone any unnecessary decisions so they didn't prevent the foundation from being merged.  The idea was to be able to merge the change so that it could be experimented with without disturbing the rest of the compiler.  After it was merged and we could get some experience with it and we could later decide what features to add later.

I was pleasantly surprised at how little the new feature affected the compiler. I didn't even need a grammar change. I just modified the lexer to detect the letter 'i' before string literals and then in the parse stage lowered those string literals to tuples.  It only affected the existing compiler in 2 places, very low impact and encapsulated.

December 13, 2018
On 12/13/18 11:36 AM, Jonathan Marler wrote:
> On Thursday, 13 December 2018 at 16:21:48 UTC, Jonathan Marler wrote:
>> On Thursday, 13 December 2018 at 16:01:52 UTC, Steven Schveighoffer wrote:
>>> On 12/13/18 10:53 AM, Steven Schveighoffer wrote:
>>>> void main()
>>>> {
>>>>      import std.stdio;
>>>>      int x = 1;
>>>>      int y = 2;
>>>>      auto s = make!(S, i"x: $(y), y: $(x)"); // name the fields to assign
>>>>      auto s2 = make!(S, i":$(x), :$(y)"); // use symbol name as field name
>>>>      auto s3 = make!(S, i"x: $(6), y: $(7)"); // use literals to assign
>>>>      writeln(i"$(s), $(s2), $(s3)"); // runtime parameters
>>>> }
>>>>
>>>>
>>>
>>> Also, this would be a lot less noisy if we didn't need the parens after the $, but the PR doesn't support that (yet?).
>>>
>>
>> Very cool use case.
>>
>> Adding support for the `$foo` case (no parens) would be fairly trival, but requires making a decision of what type of grammar node/token to use.  We could use the same mechanism that templates use, but that would mean that something like $foo.max would be $(foo).max instead of $(foo.max) which seems a bit unexpected.  That would be a good thing include in the DIP, a list of the obvious tokens/grammar nodes we could use and their pros/cons.
> 
> I should add, the strategy I used in the PR was to establish the "foundation" of interpolated strings and postpone any unnecessary decisions so they didn't prevent the foundation from being merged.  The idea was to be able to merge the change so that it could be experimented with without disturbing the rest of the compiler.  After it was merged and we could get some experience with it and we could later decide what features to add later.

So I have a beef with the assertion from Andrei that since nobody is using the current interpolation possibilities using mixins, it translates to people not wanting to do string interpolation. And my reasoning is: if it's super-ugly and complicated, people *aren't* going to use it. The beauty of it is one of the most important parts of adding it to the compiler!

And the beauty goes along with getting rid of those parentheses. With current capabilities you can get rid of the parentheses, but if interpolation goes into the compiler, it's functionality is fixed.

I think we should make a decision either way and propose it in the DIP.

> 
> I was pleasantly surprised at how little the new feature affected the compiler. I didn't even need a grammar change. I just modified the lexer to detect the letter 'i' before string literals and then in the parse stage lowered those string literals to tuples.  It only affected the existing compiler in 2 places, very low impact and encapsulated.
> 

Yeah, it's pretty sweet how it works. I very much hope we can get it in.

As people think of string interpolation possibilities, they should post threads on it. It would be good to do a study on how it looks/performs with current capabilities and with the proposed update.

-Steve
December 13, 2018
On Thursday, 13 December 2018 at 16:58:35 UTC, Steven Schveighoffer wrote:
> So I have a beef with the assertion from Andrei that since nobody is using the current interpolation possibilities using mixins, it translates to people not wanting to do string interpolation. And my reasoning is: if it's super-ugly and complicated, people *aren't* going to use it. The beauty of it is one of the most important parts of adding it to the compiler!

I think another big part of it is that currently, using string interpolation requires pulling in a dub package as a dependency. Especially in small projects, the difference between "zero dependencies" and ">=1 dependencies" is pretty huge. If `interp` were in the standard library (std.string, maybe?) it would probably see more use than it does now (at least, I'd use it in my projects).
December 13, 2018
On 12/13/18 1:19 PM, Paul Backus wrote:
> On Thursday, 13 December 2018 at 16:58:35 UTC, Steven Schveighoffer wrote:
>> So I have a beef with the assertion from Andrei that since nobody is using the current interpolation possibilities using mixins, it translates to people not wanting to do string interpolation. And my reasoning is: if it's super-ugly and complicated, people *aren't* going to use it. The beauty of it is one of the most important parts of adding it to the compiler!
> 
> I think another big part of it is that currently, using string interpolation requires pulling in a dub package as a dependency. Especially in small projects, the difference between "zero dependencies" and ">=1 dependencies" is pretty huge. If `interp` were in the standard library (std.string, maybe?) it would probably see more use than it does now (at least, I'd use it in my projects).

Having recently been doing some typescript programming, I can say that string interpolation is the kind of thing that grows on you. I could live without it, but code with it is much nicer. And more likely correct, as has been demonstrated in this thread.

I can say, however, that the barrier to entry of a library version, both in imports and in heavy syntax, are enough that I'd never use one.