Programmatically controlled template string expansion?
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 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.
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).
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,
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?
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' });
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.
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.
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!
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.
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}>
);
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,
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
.
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.
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?
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.:
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