The state of string interpolation

H. S. Teoh hsteoh at quickfur.ath.cx
Thu Dec 6 20:00:02 UTC 2018


On Thu, Dec 06, 2018 at 02:27:05PM -0500, Steven Schveighoffer via Digitalmars-d wrote:
> On 12/6/18 2:12 PM, Paul Backus wrote:
> > Marler's original proposal is simple, orthogonal, and elegant. It
> > makes use of existing D features in natural ways, and is both easy
> > to understand, and easy to use in simple cases without *needing* to
> > understand it. I think adding additional machinery like
> > FromInterpolation on top of it would be a mistake. If users want to
> > opt in to that extra complexity, it can always be made available as
> > a library.
[...]
> The more I think about it, the better it is to use the original
> proposal, and just pass the parameters at compile time in order to
> make it work. The non-string portions will be aliases to the
> expressions or variables, making them easily distinguishable from the
> string portions.
> 
> So instead of my original db.exec(...), you'd do db.exec!(...),
> exactly the same as before, just it's passed at compile time vs.
> runtime. The runtime possibility is there too, but you wouldn't use it
> in a database setting.
> 
> This can be used to forward to whatever you want. If you want to
> change the parameters to FromInterpolation("foo", foo), it will be
> possible.
> 
> This is very D-ish, too, where we supply the compile-time mechanisms,
> and you come up with the cool way to deal with them.
[...]

Yes, this.  I support this.

Now, I'm also thinking about how to minimize the "surface area" of
language change in order to make this proposal more likely to be
accepted by W&A.  Because as it stands, the exact syntax of interpolated
strings is probably going to ignite a bikeshedding war over nitty-gritty
details that will drown the core proposal in less important issues.

While it's clear that *some* language support will be needed, since
otherwise we have no way of accessing the surrounding scope of the
interpolated string, I wonder how much of the implementation can be
pushed to library code (which would make this more attractive to W&A),
and what are the bare essentials required, in terms of language change,
to make a library implementation possible.

Essentially, what you want to be able to do is essentially for a
template to be able to reference symbols in the caller's scope, rather
than in the template's scope. We want to be able to do this in a clean
way that doesn't break encapsulation (too badly).  If we could somehow
pass some kind of reference or symbol that captures the caller's scope
to the template, then we can implement interpolated strings as library
code instead of adding yet another form of string literal to the
language.  For example, perhaps something like this:

	// Note: very crude tentative syntax, bikeshed over this later.
	template myTpl(string s, import context = __CALLER_CONTEXT)
		if (is(typeof(context.name) : string))
	{
		enum myTpl = s ~ context.name;
	}

	void main() {
		string name = "abc";
		enum z = myTpl!"blah";
		pragma(msg, z);	// prints "blahabc"
	}

Basically, `context` serves as a proxy object for looking up symbols in
the caller's context. It behaves like a module import in pulling in
symbols from the caller's context into the template, as if the template
had "imported" the caller's context (like you would import a module).

Armed with such a construct, the template can actually reject
instantation unless the caller's context contains a symbol called "name"
(see the sig constraint).  This allows a string interpolation template
to produce a helpful error message when instantiation fails, e.g., by
moving the sig constraint into a static if with a static assert that
says "symbol 'blah' in interpolated string not found in caller's
context" or something to that effect.

This solves the problem of the template referencing symbols outside its
own scope that may not be guaranteed to exist.

Passing the context as a template parameter also lets the user pass in a
different scope, e.g., a module, as parameter instead:

	template findSymbols(import context)
	{
		alias findSymbols = __traits(allSymbols, context);
	}
	alias stdStdioSymbols = findSymbols!(std.stdio);

A lot of other possibilities open up here as applications, not just
interpolated strings.  Though the primary motivation for this feature
would be interpolated strings.

Just throwing out some ideas to see if there's a way to do this without
making a big change to the language that will likely be rejected.


T

-- 
To err is human; to forgive is not our policy. -- Samuel Adler


More information about the Digitalmars-d mailing list