Open dSalieri opened 2 years ago
It is some magic behavior of the tagged template strings.
https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-gettemplateobject
@Jack-Works sure, I saw that link that you showed. Your example is very interesting, but this is not still answer to my question about purpose and needing of [[TemplateMap]]
. I want to understand sense of this concept.
It allows caching results (of e.g. parsers of other languages) in association with the template literal parse node. Template tag functions can perform work related to the literal segments just once, with subsequent evaluations only needing to perform work related to the substitution values. This is what makes them "templates".
@bathos In other words, [[TemplateMap]]
caches parts of template literals as the arguments so that when the same syntax node is called again, the template literals is not parsed. That is, it is an optimization. So? Is it a micro-optimization or redundant, what do you think?
I'm asking this because I'm trying to understand the expediency of such a decision. I see that [[TemplateMap]]
was created back in ES6 and this concept has not changed. Is it really enough bottleneck where productivity seriously falls?
It enables template tag functions to cache results without having to create a key based on every argument, so yes.
That is, it is an optimization.
It can be, but it's affording an identity associated with the parse node and this enables more than just performance optimizations. It is a kind of language extension point. I would recommend studying the source code of a library like lit-html to understand what it enables.
concretely it allows you to write something like
const cache = new Map();
function myCoolTag(strings, ...values) {
if (!cache.has(strings)) {
cache.set(strings, computeComplexLogic(strings));
}
return cache.get(strings)(values);
}
myCoolTag`whatever ${1} stuff`;
the identify of the array strings
is always the same for each spec parse node, so you can very efficiently parametrize the values
.
@devsnek I am afraid, that your example won't work because each new execution of expression
myCoolTag`whatever ${1} stuff`;
will create new record in a Map.
Look at the folowing code:
function tag(){
return arguments[0];
}
tag`123` === tag`123`; /// false
but
function tag(){
return arguments[0];
}
function tagA(){
return tag`123`
}
tagA() === tagA(); /// true
So it should be the same parse Node, otherwise you won't see optimization.
@dSalieri you can think of it as the source location, here's an example: https://engine262.js.org/#gist=74ea9624bb9f61a9b1c42f7c26ce9417
@dSalieri you can think of it as the source location, here's an example: https://engine262.js.org/#gist=74ea9624bb9f61a9b1c42f7c26ce9417
That's it what I meant :)
@dSalieri It sounds like you’ve got the idea. The final expression in the example devsnek gave is the part of the source text with its “identity” exposed to ES code as the identity of a frozen array. The same array will be received by the tag function each time a specific tagged template expression (i.e., not just same-text but same “physical” source text location) is evaluated. This becomes apparent if such an expression appears within a function called multiple times or within the body of a for-loop, etc.
Earlier I suggested checking out lit-html as an example. Here is a tiny demo I made just now with the intention of illustrating aspects that are likely to be interesting. In the “updates” log panel there:
In this HTML example, keying by identity is more than optimization because the identities of the elements being created matter. The elements can be stateful in various ways beyond what’s controlled and updated by the template itself. For example there may be associated CSS animation timelines, elements may have focus, form controls may be dirty, iframes may be loading child browsing contexts, video and audio media may have a current playback position, etc. The unique identities of the elements that may carry this independent state “emerge” from the composite “weak keys” which took the parse node as the key’s “root.” Recreating those elements on every evaluation wouldn’t just be slower — it wouldn’t achieve the same thing at all.
(There may be and probably are better terms for these things that I just don’t know, btw.)
Broadly you could say that if not mapping the parse node identity to a frozen array identity, “template tags” wouldn’t be able to meaningfully “template” anything except strings (which is what untagged templates provide). The unique syntax used for template tags is itself part of this: if there were no template array being persistently associated with the parse node, there’d be no need for the special syntax, either, and they could as easily have just been regular function calls().
@bathos you've written:
Recreating those elements on every evaluation wouldn’t just be slower — it wouldn’t achieve the same thing at all.
Hmm. How do we get different results with the same template literals but different parse nodes?
How do we get different results with the same template literals but different parse nodes?
I’m not 100% sure what “the same template literals” means here, but since you’re contrasting it with the same-parse-node behavior, I’m guessing you’re asking about how one might make tag
below prepare one template based on [ "a", "b" ]
in (1) and but then reuse that same template in (2) based on the fact that the second array’s contents are also [ "a", "b" ]
, despite the expressions themselves being different parse nodes?
tag`a${ 1 }b`; // 1
tag`a${ 2 }b`: // 2
The “folded” portion of my last response mentioned that it was once true that the template arrays here would have shared identity, but they don’t anymore. String-based identity remains attainable though because string identities are undeniable/forgeable. In general this isn’t terribly expensive, either, but YMMV depending on what kinds of templates they are. It could look something like this:
let state = { __proto__: null };
let kTemplate = Symbol();
let tag = (strings, ...subs) => {
let terminus = strings.reduce((state, string) => state[string] ??= { __proto__: null }, state);
terminus[kTemplate] ??= createSomeKindOfTemplateThingOnceForThisSequenceOfStrings(strings);
// Assume the cSKOTTOFTSOS function here does the “parsing” or equiv prep and returns a new function
// that expects values that will be slotted into a specific result “instance” somehow. The cSKOTTOFTSOS
// function will run for (1), but for (2), the prior result will already be there. In both cases at this
// point the `state` object will look like `{ a: { b: { [kTemplate]: fn } } }` and `terminus === state.a.b`.
return terminus[kTemplate](subs);
};
But note that this state could continue growing indefinitely — which is the exact problem the original behavior faced. There’s no way to know when a forgeable primitive like a string is “collectable”, so in turn you don’t know if the prepared template is collectable, either. Not ideal.
(This may also help clarify what ljharb meant by “It enables template tag functions to cache results without having to create a key based on every argument” if that hadn’t already clicked before.)
@bathos sorry, I should have made my question more precise. Let's do it again. You wrote:
Recreating those elements on every evaluation wouldn’t just be slower — it wouldn’t achieve the same thing at all.
I asked the question:
How do we get different results with the same template literals but different parse nodes?
Under that I mean following:
function tag(){
return arguments[0];
}
/// (1) (2)
tag`123` === tag`123`; /// false
/// (1),(2): same template literals but different parse nodes
How can I get different results by equal template literals? Your quote: it wouldn’t achieve the same thing at all
.
How do we get different results with the same template literals but different parse nodes?
You wouldn't if "result" here means the template string array itself. The template string arrays will be the same, but this isn't the end result.
I think I see what might be throwing you off though as the example I gave creates two "instances". The "template" (result of the initial parsing step) is what is keyed by the template array. The template in turn holds a WeakMap that maps host nodes to "live" instances which the template spawns. This means the final 'instances' are keyed by unique pairs of identities: one template string array plus one "rendering target" host node. This is how you end up parsing once (template array identity) but rendering two unique instances (template array + host node). Roughly, anyway. I'm eliding some (many, actually!) details and only meant to get the gist of what's in play across. This is one particular example/pattern out of many that are possible, and although I think it provides a solid illustration, there's a risk that overemphasizing aspects of this that are more unique to the needs of HTML templating could end up making things more confusing.
@bathos excuse me for repeating myself, but could you demonstrate your words?
Recreating those elements on every evaluation wouldn’t just be slower — it wouldn’t achieve the same thing at all.
Could I look at this behaviour practically? I want to see confirmation of your words.
P.S All that you've written upper I read.
I provided a glitch demo earlier that illustrates interpolated-values-only updates where the static portions of the template string (corresponding to an HTML element in the example) are likewise static, i.e. their identity is preserved across every update. If you poke around in the console there maybe it’ll be more evident what I meant? For example you could run a CSS animation on them: if the elements were being recreated/replaced every time it updated, then the animation would also reset every second instead of continuing to run. Perhaps I’m misunderstanding your question, not sure.
@bathos yeah, I saw your demonstration that based on caching the same parse node. The thing is what I asked to show visa versa behaviour of template literals when recreating elements wouldn't achieve the same thing
if the elements were being recreated/replaced every time it updated, then the animation would also reset every second instead of continuing to run
Well, it is not necessary to recreate the element, it is enough to mutate it. Your example does just that.
I think I kind of understand you.
You know if you look at the definition of realm you will see following:
That's all.
Well, there is no definitions about special fields. But if we look at the table 27 we will see something...
Since, I am interested in
[[TemplateMap]]
, I will ask about it. There is"meaning"
in the table, but table don't tell you about her purpose, it says about structure of[[TemplateMap]]
, that's it, and there are no somewhere words about purpose of[[TemplateMap]]
. Hence you don't understand purpose, if you're not maintainer of ecma262. So my question is "What is[[TemplateMap]]
actual, why does the specification need the[[TemplateMap]]
?"P.S
[[TemplateMap]]
is the only thing you can't figure out by looking at the spec in the realm. Would like to see an explanation not only here but also in the specification.