December 07, 2018
On Friday, 7 December 2018 at 03:00:54 UTC, Adam D. Ruppe wrote:
> On Friday, 7 December 2018 at 02:38:30 UTC, Mike Franklin wrote:
>>> We kinda-sorta can, with `lazy void`.
>>
>> I'm not sure what you mean.  Please elaborate.
>
> It is the runtime version of what Steven gave.
>
> void foo(lazy int something) {
>    import std.stdio;
>    writeln(something);
> }
>
> void main() {
>   int a = 1, b = 2;
>   foo(a + b);
> }
>
>
> And you can `lazy void` too, which takes anything which has an effect (it is an error if it has no effect), but discards its return value. example here: https://dlang.org/spec/function.html#lazy-params

It's stuff like this that makes D so cool; I wasn't aware of this feature.  Thanks, Adam, I totally get it now.
December 07, 2018
On Friday, 7 December 2018 at 00:43:43 UTC, Mike Franklin wrote:
> I know I'm just thinking out loud and not contributing much, but I can't help but feel that there's something common in all these ideas that just hasn't been identified yet.  And no, Jacob, not AST macros (Actually, that would probably do it, but I think we've already been there) :D
>
> Mike

The truth is, we already have a feature that allows us to do this, its "mixin".  I empathize with the feeling that there must be something else out there that allows us to do interpolation as a library, but I think you'll find there just isn't.

As D exists today, with a library solution, we need 2 extra "composed" function calls to `mixin` and the "interpolating" function. Plus we need an extra import to include the interpolating function.

import std.typecons : interp;
writeln(mixin(interp("a is $(a)")));

Even if we could make mixin a little more friendly, you still have:

import std.typecons : interp;
writeln(interp("a is $(a)").mixin);

These 2 extra calls are the difference between string interpolation spreading like an infectious disease and being completely useless. String interpolation in this form is laughable when you compare it to existing solutions:

writeln("a is ", a);
writeln("a is %s", a);

For comparison, here's the simple language solution for it:

writeln(i"a is $(a)");

Looking for a feature that can enable "interpolation as a library" as well as other constructs is the right question. You're looking for a feature that's almost as powerful as mixin but also doesn't require the caller to be so verbose.  This is where the rubber meets the road.  Sadly, no such feature can or should exist.  If you allow functions to have this power you are going to make the language impossibly hard to understand moving forward.  Any function now has the ability to reach into their caller's scope meaning you now have to understand the source of every function to know how it affects any and all of your local data.  There's no more isolation, local variables are now almost as bad as global variables **shudder**.

Interpolation is an interesting case because it can reach into the caller's scope, but all the code to access locally scoped state is inside the string being interpolated at the call site.  Here's the way I see it:

   Allowing a library to write functions that reach into their caller's scope breaks the assumed isolation between functions.  This means that no data is safe, any function could mess with your data at any time. We don't want that, so this means if we want interpolation to be a library, then the caller MUST invoke `mixin`. But for string interpolation to be adopted, it needs to be less verbose than current solutions which is impossible if it needs to use mixin. Thus we arrive at an impass.  We want to implement interpolation as a library but it can't be done well enough unless we break function encapsulation.  Assuming all of this logic is correct, the only conclusion is that interpolation requires language support.

All that being said, if someone does find a feature that allows interpolation as a library without mixin and without breaking function isolation then great.  But no feature has been found yet and my confidence that no such feature can exist is growing over time. At some point we'll have to admit the unicorn doesn't exist so let's stick a horn on our horse and paint it...it's not as pretty as the unicorn, but at least its real :)

December 07, 2018
On Friday, 7 December 2018 at 03:29:11 UTC, Steven Schveighoffer wrote:

> As long as there is an insistence that 'if it's doable in the library, the elegance is not worth the effort,' any proposal will fail. We need to first convince those in charge think the feature should be added because of the *human* impact. That was the point of my longer answer.

Understood.  In Jonathan's PR Andrei challenged the merit of a language string interpolation feature because a library solution already existed, and it wasn't being heavily utilized.  I think that overlooks the fact that perhaps the reason the library solution is not heavily utilized is because its syntax is not very elegant.  But I think he also simply wanted the potential of a library solution to be thoroughly investigated.  That is what is supposed to happen in the DIP process.

I still think there could be some general, broadly useful features to add or limitations to remove so we could build something like this without having to justify it to anyone.

>> Jacob mentioned AST macros (I knew that was coming, and even had a note in my first draft say "Sorry, Jacob, except for AST macros", but I deleted it :D ).  But Jacob is on to something. We already pass around symbols with `alias`.  Can that be extended somehow to pass around expressions too (i.e. expressions as rvalues)?  That would probably help address the issue of printing assert expressions as well (https://github.com/dlang/dmd/pull/8517)
>
> I think we can't exactly pass around expressions in the general case, because it's too fraught with ambiguity.

[..]

> However, if I had to give it a name (and syntax), I'd say it's a lazy alias.

Yep, that's one of the features I had in mind.  We already have it at runtime; would be nice to have feature parity at compile-time.  Maybe it wouldn't do anything to help the problem at hand though.  I'll have to spend some time on it.

Mike


December 07, 2018
On Friday, 7 December 2018 at 01:47:13 UTC, H. S. Teoh wrote:

> And yes, `mixin interp!"..."` is horribly verbose. But ostensibly we could abbreviate it to something like `mixin i!"..."`.

Perhaps what's needed is a different mixin operator that is not verbose and significantly distinguishes it from the template instantiation operator.

mixin i!"...";  // instead of this...  (too verbose)
i!"...";        // or this... (looks too much like a template instantiation)
i#"...";        // we create a new mixin operator `#`

Mike

December 07, 2018
On Friday, 7 December 2018 at 05:38:13 UTC, Jonathan Marler wrote:
> All that being said, if someone does find a feature that allows interpolation as a library without mixin and without breaking function isolation then great.  But no feature has been found yet and my confidence that no such feature can exist is growing over time. At some point we'll have to admit the unicorn doesn't exist so let's stick a horn on our horse and paint it...it's not as pretty as the unicorn, but at least its real :)

I've long wished for something like the __CALLER_CONTEXT__ that's been suggested elsewhere in this thread, but as has been pointed out, it's a maintenance nightmare.

An idea struck me, though: what if __CALLER_CONTEXT__ needed to be explicitly passed, and i"" is simply syntactic sugar?

Specifically, i"value: {a+b}" is lowered to interpolateString!("value: {a+b}", __traits(getContext)). interpolateString is a template in druntime that does the heavy lifting.

This would require two new features:

1) __traits(getContext), which returns a scope that can be passed to templates, and has some way of invoking mixins in the context of that scope (for stuff like i"value: ${a+b}", where simple member lookup isn't enough), possibly as context.mixin("a+b").

2) A lowering from i"{a+b}" to interpolateString!("{a+b}", __traits(getContext)).

This way we get a shiny new feature to play with and see if there are other valuable uses (__traits(getContext)), and a neat way to implement string interpolation without destroying every expectation of encapsulation. Strictly speaking, interpolateString could still do stupid things, but I'd argue we should be able to trust code in druntime.

--
  Simen
December 07, 2018
On Thursday, 6 December 2018 at 22:31:07 UTC, Jonathan Marler wrote:
> There's a reason why there's no way to access the callers scope without a mixin...because it breaks encapsulation.  Imagine this:
>
> int x = 10;
> foo();
>
> What is the value of x after this?  If a function (or whatever foo is) could access the caller's scope, then it could be anything.

I agree, it is thin ice. But Mike asked for a generalisation and this is it.

Also, scala's implicit parameters do also allow mutation. Although in scala stuff is generally immutable to begin with.

> Interpolated strings are a special case because even though they can access the caller's scope, they do this explicitly:
>
> int x = 10;
> foo(i"$(x=20)");
>
> You can clearly see that x is being set to 20, you don't need to know the implementation specifics of interpolated strings to see that.

Good point.

> On that note, you could make an argument that you should be able to access the caller's state in a "read-only" capacity,

I was about to say that.

> but that can get complicated.  Say you have this:
>
> int x = 10;
> int* y = &x;
> foo();
>
> What's the value of x after this?  What if we define foo as:
>
> void foo(CallerScope s = __CALLER_SCOPE__)
> {
>     *(cast(int*)s.y) = 20;
> }
>
> We did have to cast `y` from const(int*) to int*, but we were able to change x nonetheless. You end up with the same problem that you have to understand every function you call at a much deeper level to know when it can modify your local state.

Same problem indeed, but one with a lower probability.

Still thin ice, for sure. But I have to say a small part of me likes it (besides enabling string interpolation, it would also enable things like 'importing' user defined functions in a library without explicitly passing them via a template parameter.)
December 07, 2018
On Friday, 7 December 2018 at 07:53:14 UTC, Mike Franklin wrote:
> On Friday, 7 December 2018 at 01:47:13 UTC, H. S. Teoh wrote:
>
>> And yes, `mixin interp!"..."` is horribly verbose. But ostensibly we could abbreviate it to something like `mixin i!"..."`.
>
> Perhaps what's needed is a different mixin operator that is not verbose and significantly distinguishes it from the template instantiation operator.
>
> mixin i!"...";  // instead of this...  (too verbose)
> i!"...";        // or this... (looks too much like a template instantiation)
> i#"...";        // we create a new mixin operator `#`
>
> Mike

When I tried - some time ago - to find a way to be able to just write

exho!"${name} you are app. ${age*365} days old";

instead of - when using scriptlike (DUB package)

import scriptlike;
...
writeln(mixin(interp!"${name} you are app. ${age*365} days old"));

(Sorry for the naming of the function was to distinguish it from echo.)

I defined an additional modul exho.d which
only defines an mixin sting:

module exho;

enum use_exho="auto mixinter(string x)(){return mixin(interp!x);}
auto exho(string x)(){return mixin(\"writeln(\"~interp!x~\")\");}";

Now in use it comes down to this:

import scriptlike;
import exho;
//Now you can write in  every scope you need it: mixin(use_exho);
// or mixinter! as shortcut for mixin(interp!...)
void main()
{
  auto name = userInput!string("Please enter your name");
  auto age = userInput!int("And your age");

  mixin(use_exho);
  exho!"${name} you are app. ${age*365} days old";
}

You still need "mixin" to get the definition in the right scope to define your shortcut in place.





December 07, 2018
On Thursday, 6 December 2018 at 11:03:23 UTC, Zoadian wrote:
> would it be possible to have some kind of eponymous mixin templates in the language?
> I'd imagine it to look something like this:
>
> ````
> import std.stdio;
>
> mixin template interp(string S) {
>     mixin interp = S; //some fancy code to replace ${_var_} with content of _var_
> }
>
> void main() {
>     int a = 5;
>     string b = interp!"hello ${a} world";
> }
> ```

Not sure if its the same thing you're thinking of, but please see this already filed enhancement request:  https://issues.dlang.org/show_bug.cgi?id=18586

Mike
December 07, 2018
On Friday, 7 December 2018 at 07:53:14 UTC, Mike Franklin wrote:
> On Friday, 7 December 2018 at 01:47:13 UTC, H. S. Teoh wrote:
>
>> And yes, `mixin interp!"..."` is horribly verbose. But ostensibly we could abbreviate it to something like `mixin i!"..."`.
>
> Perhaps what's needed is a different mixin operator that is not verbose and significantly distinguishes it from the template instantiation operator.
>
> mixin i!"...";  // instead of this...  (too verbose)
> i!"...";        // or this... (looks too much like a template instantiation)
> i#"...";        // we create a new mixin operator `#`
>
> Mike

An interesting idea.

Maybe id#(...) lowers to mixin(id(...)) Or something.

Keep in mind though, that interpolated strings implemented using this will have the disadvantages of mixin code, bad error messages, hard to debug and a hit to compile time performance. Plus you still need the extra import. Even if we could implement interoplated strings this way, it may still be worth it to add the small amount of code to the compiler for the extra benefits. But this idea is the first I heard that could work.
December 07, 2018
On Friday, 7 December 2018 at 14:40:05 UTC, Jonathan Marler wrote:
> On Friday, 7 December 2018 at 07:53:14 UTC, Mike Franklin wrote:
>> On Friday, 7 December 2018 at 01:47:13 UTC, H. S. Teoh wrote:
>>
>>> And yes, `mixin interp!"..."` is horribly verbose. But ostensibly we could abbreviate it to something like `mixin i!"..."`.
>>
>> Perhaps what's needed is a different mixin operator that is not verbose and significantly distinguishes it from the template instantiation operator.
>>
>> mixin i!"...";  // instead of this...  (too verbose)
>> i!"...";        // or this... (looks too much like a template instantiation)
>> i#"...";        // we create a new mixin operator `#`
>>
>> Mike
>
> An interesting idea.
>
> Maybe id#(...) lowers to mixin(id(...)) Or something.
>
> Keep in mind though, that interpolated strings implemented using this will have the disadvantages of mixin code, bad error messages, hard to debug and a hit to compile time performance. Plus you still need the extra import. Even if we could implement interoplated strings this way, it may still be worth it to add the small amount of code to the compiler for the extra benefits. But this idea is the first I heard that could work.

One more thing.  If we implement it as a string operator (i.e i"...") then we can use just the letter 'i' because it doesn't live in the identifier namespace.  If we made it a library, we should probably give it a longer name (i.e. interp) so we don't take away the identifier `i` from the app.

writeln(i"a is $(a)");

vs

import std.typecons : interp;
writeln(interp#"a is $(a)");

There are a handful of benefits to making it a part of the language and the importance of these benefits should be carefully considered.  It's the shortest syntax, has normal error messages, fast compile time performance, no need for extra imports or definitions, does not take away identifiers/symbols from the app and just looks nicer. If string interpolation is going to be a heavily used feature, showing up in code samples promoting the language and all over the standard library then it deserves the language support for these benefits.  If it was accepted it would show up all over the place in my code and we want to make a good impression.