Programmatically controlled template string expansion?

# Antony Courtney (10 years ago)

Many template systems provide a programmatic mechanism (usually a simple function call) that can be used to perform template expansion based on passing the template string and some object that serves as mapping dictionary, e.g.:

expand(`Hello ${x} and ${y}!`, {x: 10, y: 12});
// ==> Hello 10 and 12!

From what I've seen of drafts of ES6 template strings, templates are

expanded automatically by evaluating the expressions enclosed in ${...} at the point where the template string literal appears.

Do ES6 templates offer this more conventional, explicit, programmer-controlled form of expansion in any form? It doesn't appear so from my reading of the draft specs, but I thought I'd check. And it's somewhat understandable, since any expression can appear inside ${...}, not just variable identifiers.

My particular application is that I want to use a template system to construct SQL queries in a modular way. For this particular application I want programmatic control over when and how the expansion happens and won't have the escaped template expressions in scope at the point where the template string literal appears. So I'll probably just use ES6 template strings because they can span multiple lines, but use Mustache.js instead of ES6 for expansion. But I'd be happy to learn of a better alternative.

Thanks,

-Antony Courtney

# Brendan Eich (10 years ago)

Antony Courtney wrote:

expand(`Hello ${x} and ${y}!`, {x: 10, y: 12});
// ==> Hello 10 and 12!

From what I've seen of drafts of ES6 template strings, templates are expanded automatically by evaluating the expressions enclosed in ${...} at the point where the template string literal appears.

Do ES6 templates offer this more conventional, explicit, programmer-controlled form of expansion in any form?

Yes, or least this was part of the template strings proposal for ES6

# Brendan Eich (10 years ago)

Brendan Eich wrote:

safe_html <${x}>hello, ${u}</${x}>

But you're right, the people.mozilla.org/~jorendorff/es6-draft.html copy I'm finding "template" in online does not include this prefix form. Did it get cut from ES6? I thought the only debate was whether to include unprefixed templates. I'm no doubt forgetting something, so cc'ing Allen.

Thanks to Domenic for reminding me to look harder:

people.mozilla.org/~jorendorff/es6-draft.html#sec-tagged-templates

Tagged templates are in ES6.

# Till Schneidereit (10 years ago)

On Tue, Oct 28, 2014 at 1:44 PM, Brendan Eich <brendan at mozilla.org> wrote:

Brendan Eich wrote:

safe_html <${x}>hello, ${u}</${x}>

But you're right, the people.mozilla.org/~ jorendorff/es6-draft.html copy I'm finding "template" in online does not include this prefix form. Did it get cut from ES6? I thought the only debate was whether to include unprefixed templates. I'm no doubt forgetting something, so cc'ing Allen.

Thanks to Domenic for reminding me to look harder:

people.mozilla.org/~jorendorff/es6-draft.html#sec-tagged-templates

Tagged templates are in ES6.

And you can play with them in Firefox (Beta 34 or newer should work).

# Antony Courtney (10 years ago)

Thanks for the quick reply.

My impression from reading the draft spec and looking at slide 28 of Domenic's excellent presentation ( www.slideshare.net/domenicdenicola/es6-the-awesome-parts )

is that with your example: … safe_html <${x}>hello, ${u}</${x}>

if this were run in a context where x is 'strong', and u is 'world', then what would be equivalent to calling:

safe_html( { raw: ['<', '>hello, ', '<', '>'], cooked: ['<', '>hello, ', '<', '>'] }, 'strong', 'world', 'strong' );

While I can see how this is useful in many circumstances, I don't immediately see how this enables the more conventional model of expansion with an explicit dictionary under control of the caller.

I'd like to be able to do something like:

function safe_html(template_string) {
    // note: mapping dictionary d constructed explicitly, not based on

what's in scope where template literal appeared var d = { x: 'strong', u: 'world'}; return template_expand(template_string, d); }

I think this requires the expansion function to be passed the exact template string before any processing has taken place, since even 'raw' form removes variable references. As far as I can tell this unprocessed form of the template string isn't made available to the expansion function in the current draft spec. Please let me know if I'm missing something.

Thanks,

# Brendan Eich (10 years ago)

Antony Courtney wrote:

I don't immediately see how this enables the more conventional model of expansion with an explicit dictionary under control of the caller.

You don't need a template string if you want full quotation without interpolation -- a string will do.

But why do you want a library API taking a string? Are you looking for the almost-verbatim (raw, multiline) nature of ... without the ${...} processing? Or are you looking for a user-supplied dictionary? Or both, possibly?

# Domenic Denicola (10 years ago)

If I am understanding you correctly, I think what you want can be accomplished using the pattern

function templater(values) {
  return `hello ${values.person} it is a ${values.quality} day today`;
}

templater({ person: 'Antony', quality: 'reasonably good' });
# Rick Waldron (10 years ago)

On Tue, Oct 28, 2014 at 5:51 PM, Domenic Denicola <d at domenic.me> wrote:

If I am understanding you correctly, I think what you want can be accomplished using the pattern

function templater(values) {
  return `hello ${values.person} it is a ${values.quality} day today`;
}

templater({ person: 'Antony', quality: 'reasonably good' });

Per Anthony's OP:

"My particular application is that I want to use a template system to construct SQL queries in a modular way. For this particular application I want programmatic control over when and how the expansion happens and won't have the escaped template expressions in scope at the point where the template string literal appears. "

Which cannot be solved in the way you've suggested, as the program won't have any kind of static knowledge of the template form itself.

# Brendan Eich (10 years ago)

Antony Courtney wrote:

function safe_html(template_string) {

// note: mapping dictionary d constructed explicitly, not based on what's in scope where template literal appeared var d = { x: 'strong', u: 'world'}; return template_expand(template_string, d); }

Part of a realistic safe_html would be checking for closing > and other

mischief smuggled into the value of x. That wants x to be evaluated first. So template strings do help a safe_html use-case.

But to make the code above more realistic, let's say the function is called strong_world ;-). The API would be

e.innerHTML = strong_world("<%{x}>Hello, %{u}</%{x}>");

and template_expand would look like this:

function template_expand(s, d) { return s.replace(/%{(\w+)}/g, (s, p) => d[p]); }

This is all a bit simplistic, of course -- for one thing, only one word naming the dictionary property to interpolate is allowed in %{...}. But you get the idea.

You're right, there's no way to use template strings without double-quoting and eval'ing to strip the outer quotes, and using with instead of regexp lambda-replace to find the dictionary properties as lexical identifiers -- all of which would suck:

function strong_world(s) { var d = { x: 'strong', u: 'world'}; return template_expand(s, d); }

function template_expand(s, d) { with (d) return eval(s); }

print(strong_world("<${x}>Hello, ${u}</${x}>"));

It's hard to separate the parsing of a template string from eager evaluation of the expressions to interpolate. Sweet.js could do it, but template strings are not the tool you want, and we do not want deferred expression evaluation as part of them -- wrong default, requires eval for the safe_html-like cases they serve.

# Brendan Eich (10 years ago)

Rick Waldron wrote:

Per Anthony's OP:

"My particular application is that I want to use a template system to construct SQL queries in a modular way. For this particular application I want programmatic control over when and how the expansion happens and won't have the escaped template expressions in scope at the point where the template string literal appears. "

Which cannot be solved in the way you've suggested, as the program won't have any kind of static knowledge of the template form itself.

Right. This is why we see with (and direct eval) still used for template processing. An alternative would generate a function taking formal parameters named by the dictionary's properties, and calling it with the corresponding values in the same order (Object.getOwnPropertyNames). Fun!

# Antony Courtney (10 years ago)

That all makes good sense, thanks.

To be clear: I'm not lobbying for a change to the spec. For now I just wanted to make sure I understood the template expansion model in the current draft spec and what it can and can't do.

I need to spend more time on my project to determine if I can make it work within the expansion model of ES6's templates or whether I truly need explicit control over the dictionary and the expansion. Even if I do need such additional expressive power it's not clear this would really necessitate any change to the spec: I could use an alternative existing template library like Mustache.js or construct an even simpler one along the lines Brendan outlined or Python's string.Template() as described here: legacy.python.org/dev/peps/pep-0292

Thanks for the clarification.

# Kevin Smith (10 years ago)

I'd like to be able to do something like:

function safe_html(template_string) {
    // note: mapping dictionary d constructed explicitly, not based

on what's in scope where template literal appeared var d = { x: 'strong', u: 'world'}; return template_expand(template_string, d); }

Does it help to think in terms of arrow functions? We're assuming that the caller knows about the template and the callee knows about the data. If so, then we can do something like this instead:

let formatted = getData(data => <${data.x}>hello, ${data.u}</${x}>);

# Antony Courtney (10 years ago)

On Tue, Oct 28, 2014 at 4:12 PM, Kevin Smith <zenparsing at gmail.com> wrote:

I'd like to be able to do something like:

function safe_html(template_string) {
    // note: mapping dictionary d constructed explicitly, not based

on what's in scope where template literal appeared var d = { x: 'strong', u: 'world'}; return template_expand(template_string, d); }

Does it help to think in terms of arrow functions? We're assuming that the caller knows about the template and the callee knows about the data. If so, then we can do something like this instead:

let formatted = getData(data => <${data.x}>hello, ${data.u}</${x}>);

Nice. Yes, with the reduced syntactic overhead afforded by =>, this could

indeed be quite helpful for my use case. I'll keep it mind.

Thanks,

# Brendan Eich (10 years ago)

I wrote:

function template_expand(s, d) { return s.replace(/%{(\w+)}/g, (s, p) => d[p]); }

No harm, but the arrow's first param should be _ (for don't-care), not shadowing-harmlessly s.

# Allen Wirfs-Brock (10 years ago)

On Oct 28, 2014, at 4:39 PM, Antony Courtney <antony.courtney at gmail.com> wrote:

Thanks for the quick reply.

My impression from reading the draft spec and looking at slide 28 of Domenic's excellent presentation ( www.slideshare.net/domenicdenicola/es6-the-awesome-parts )

is that with your example: … safe_html <${x}>hello, ${u}</${x}>
if this were run in a context where x is 'strong', and u is 'world', then what would be equivalent to calling:

safe_html( { raw: ['<', '>hello, ', '<', '>'], cooked: ['<', '>hello, ', '<', '>'] }, 'strong', 'world', 'strong' );

This isn’t quite right. It should be:

safe_html( Object.freeze(Object.defineOwnProperty(['<', '>hello, ', '<', '>’], ‘raw’, {value: ['<', '>hello, ', '<', '>’]}), 'strong', 'world', 'strong' );

See people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-gettemplatecallsite

This above example still isn’t perfect because it does not canonicalize the call site object.

# Brendan Eich (10 years ago)

Antony Courtney wrote:

Does it help to think in terms of arrow functions?  We're assuming
that the caller knows about the template and the callee knows
about the data.  If so, then we can do something like this instead:

   let formatted = getData(data => `<${data.x}>hello,
${data.u}</${x}>`);

Nice. Yes, with the reduced syntactic overhead afforded by =>, this could indeed be quite helpful for my use case. I'll keep it mind.

This is pretty sweet if you know the property names. But I inferred (Rick did too) that in some cases you won't. True?