Open jamiebuilds opened 3 years ago
+1 for String.cooked
.
On reflection, I also like String.interpolate
. Sorry to be unhelpful!
Pro for String.identity
is that if you're doing something that's meant to take a tag function (and which will then call the function on a template string at some point), identity
immediately suggests its role as a default/no-op value; none of the other names do. Con is that it suggests you'll get the exact string you put in if there is no interpolation, which isn't true - the escapes are resolved.
I quite like cooked
. It's a little silly, but it has several good points for it:
Not a fan of interpolate
- all tag functions interpolate (assuming you're referring to the ${}
segments). The important notion to capture here is what it does to the literal segments, vs raw
.
Since you're looking to match the behaviour of an untagged template, why not something like String.asIfUntagged
or just String.untagged
? It can be thought of as the "implicit tag" for a template when one isn't provided.
In a recent refactoring of TV and TRV, we chose to refer to the cooked properties as the "indexed components" of the template (since the only reference to the term "cooked" currently is in alias names), in contrast to the "raw components". Though I think String.indexed
doesn't really do the job here.
I am fine with either of String.interpolate
or String.identity
. I would avoid String.cooked
since, as mentioned above, it's not normatively referenced or defined in the spec, and the usage of the term seems to be TC39-internal.
I would avoid
String.cooked
since, as mentioned above, it's not normatively referenced or defined in the spec, and the usage of the term seems to be TC39-internal.
raw
is in the language, and cooked
is an obvious analogue to raw
which suggests precisely the behavior it has.
I had no idea we even used the term "cooked" internally in the spec. It just sounds like a nice analogue, and lightly conveys the precise meaning intended here - the string pieces have been allowed to "mature" and change to their final forms.
One downside for names like String.zip
, String.interleave
, possibly String.interpolate
, and arguably String.identity
is that their method names on their own are common names of much more generic functions in other languages (i.e. IterTools.interleave
) / JS libraries (i.e. lodash.identity
), and are names we may consider in the future for an IterTools JS proposal, or something else.
Also to mention, I've seen a number of compilers in different languages use the term "cooked" to distinguish it from "raw". In Rome we use it like this:
jsTemplateElement.create({
cooked: "",
raw: "",
})
"cooked" its not exactly common nomenclature in JS. I'd suggest String.interpolate
or String.interp
. Having String.interpolate
doesn't preclude other kinds of interpolate
functions/methods from other sources with a different meaning. I'm not sure how often I see interpolate
as a standard function (I've generally seen either lerp
for linear interpolation or tween
for animation frame interpolation/easing). Also, the fact it is namespaced on String
makes it fairly obvious its intended use.
FWIW, MDN refers to "cooked" strings within their template literal documentation. Excerpt:
However, illegal escape sequences must still be represented in the “cooked” representation. They will show up as undefined element in the “cooked” array:
function latex(str) {
return { "cooked": str[0], "raw": str.raw[0] }
}
latex`\unicode`
// { cooked: undefined, raw: "\\unicode" }
Could it be String.parsed
? Just like JS engine parses template literals. For me 'cooked' feels like a synonym for 'parsed'.
@GreLI that's an interesting angle.
It might be a bit of a squatting hazard given JS does already have Number.parseFloat - which parses a regular number token (afaik; not 100% sure it's free of quirks) and analogous companions (Boolean.parse, String.parse) don't strike me as too far fetched to consider leaving space for. Tags can only address raw/cooked string values rather than string tokens - the former are products of parsing, the latter is input to parsing (w/ the quotation marks, etc).
OTOH, "parsed" is not "parse", and I can see how that could make the difference.
Speaking of that distinction: I think parse v parsed is a great example of why nouny/adjectival naming for tags may be preferable over verb names in general - it seems particularly clear when contrasting these that the first would be "off". Raw is adjectival, too, and while a precedent of one may not count for much, (anecdotally) it seems like the convention of tag names communicating something the string is rather than what's being done with it is pretty widespread (e.g. "html", not "createTemplate"). So (for example) I'd suggest "interpolated" is better than the previously discussed "interpolate".
At first glance, the past-tense parsed
doesn't feel much like JavaScript's existing stdlib, I'm not sure if anything is named that way, but a quick look at MDN for global object methods the only one I was able to find was Promise.allSettled
In a more general sense, "parsing" to me implies syntactic analysis of a string or the transformation between data types, neither of which feel terribly relevant here.
Looking around for alternatives I found that Golang's grammar uses "raw" vs "interpreted". I have similar reservations as I do parse
, but I think methods named interpret
are few and far in between so maybe that's fine.
I wanted to use this function in a template tag the other day in the way suggested in the readme:
function myTag(strings, ...exprs) {
let transformedStrings = strings.map(someTransformation);
return String.cooked(transformedStrings, ...exprs);
}
This made me like cooked
less. By the time I've prepared the data (in this example), it's no longer a template-strings array: it's just an arbitrary array of strings.
That is, I've already processed the strings at the point at which I want to call this function. So any name which suggests that it is processing the strings themselves, rather than just mashing them together, is a bad name. (For example, parsed
has exactly the same problem as cooked
.)
So my vote is now for interpolate
or similar.
(This usage also makes it clear, I think, that identity
is a bad name. identity
only makes sense when it's used as a template tag, not when used for building other template tags.)
I could also see a case for splitting out the two use cases: have a String.interpolate(array1, array2)
, for use in building template tags, and a String.identity(array1, ...exprs)
for use as a default template tag.
How about pasteurized
?
I like String.interpolate
the most.
I think cooked
is good, better not to worry about it and be happy with a carefully considered name even if there are other options. It's a feature, not a bug, that it uses a different term than golang even though I like golang and think their name is also fine. Being a contrast against raw, it is a solid choice.
I think I might have mentioned this in a TC39 plenary meeting at one point, but I couldn't find it in the notes with a quick look. In general I think using puns to name things can be confusing for developers (numbering likely in the millions or tens of millions worldwide) who aren't fluent in English.
I suppose with general purpose translation tools "interpolated" would do better but with docs in an editor or a search it would be quick to find the meaning.
As for raw
vs cooked
, there are a lot more definitions for raw
than uncooked
. So it's not necessarily raw as in food, though I'm sure a lot think of that. I have thought of it as raw
as in material, like raw wood. The word to describe prepared wood isn't "cooked". Even for food it breaks down w/ pasteurized as I mentioned.
I might have changed my choice to interpolate
. 🤔
Tag names should be descriptive/declarative, not verbs. I’m not married to a particular name, but I think that factor is important. Old meeting notes suggest this reasoning was used for raw
itself, and in the years since, the ecosystem has generally stayed true to this convention too — tags get named things like html
, not parseHTML
, etc.
A name like interpolated
would meet that criteria, but the “imperative” interpolate
would not. In any case, I believe the latter should be reserved for a non-tag utility method, which has its own reasons to exist.
It's only technically a tag name, though. It doesn't need to be a particularly good example of what to call a userspace tag name IMO.
I do agree that they aren't imperative verbs like a function. I think the past participle would work. So I suppose String.interpolated
would objectively be better.
Maybe that can be considered descriptive and declarative even though it's just changing the tense.
I have thought of it as
raw
as in material, like raw wood.
Yes, I'm pretty sure that's the intended original meaning. It returns the raw source code string. I would argue that the opposite term is processed
, not cooked
. I also like parsed
or interpolate(d)
, possibly interpolateTemplate
if required for disambiguation, but please avoid the food analogy.
Hi !
I have an utility String.merge
in my utility repo for years doing this... (maybe with some differences as this spec, like defaulting undefined
values to ""
to don't throw)
Definition here:
https://github.com/devingfx/vanill.es/blob/dev/String/merge.js
Referenced as String.merge
here:
https://github.com/devingfx/vanill.es/blob/4910bdd4fb4c8ecf7cf0f58779ec335c4fddbde2/String/index_.js#L8
Why merge
?
Because this is more about "concatenating", "sticking", "zipping" the strings parts and the placeholder values, that an inverse of the "raw" notion in my head...
It is used to kinda "get what would be the result of untagged string" but in an actual string tag function... Example: https://github.com/devingfx/vanill.es/blob/dev/DOM/CSS.js
merge
sounds very similar to me as concat
, which is basically just +
.
merge
sounds very similar to me asconcat
, which is basically just+
.
Yes exaclty !
Which is basically what this method is doing:
String.merge`Hello ${who} !` === 'Hello ' + who + ' !'
Interesting proposals, I do love the symmetry of String.cooked
with String.raw
, but I also agree that taken independently "cooked" is a bit unclear.
String.cooked
: I don't think it would be wrong to just go with it. But "cooked" really doesn't have much meaning except as contrast to "raw" but in either case it's idiomatic, though "raw" is much more commonly used.String.interpolate
: Does what it says, but it's a bit long and is almost too on the nose. The thing the backticks do is interpolation, introducing a tag called interpolate might make it look to someone reading the code as if the tag was actually required in order for interpolation to take place. String.identity
: Makes sense to me, but I wonder if someone less experienced, or not as familiar with English, might struggle to understand what it's supposed to mean. If you look up "interpolate" a clear definition will be presented. And while "cooked" is idiomatic, it's the same idiom as "raw", but "identity" only makes sense because of its mathematical definition. Additionally, even understanding the word I might not realize it means "do nothing" and might think it does something like not interpolate - that is, give me exactly what's in the back-ticks.String.parsed
: I feel like this would imply that it does something more like eval()
than interpolation, and failing that it has the same issue that interpolate has where it might imply that the tag is needed to do what the template does on its own.String.merge
: At first I kind of like this one, but it also has the same issue as the last one.Over all, I sort of think the problem is that most of these are verbs (or adjectival uses of things that are also verbs). Only identity
stands out as not implying that the tag does something or returns the result of any process. But it also goes a bit too far in that aspect, in that one might think it causes the template to actually do less than a normal template.
As an alternative, might I present:
String.tag
You might being thinking "that doesn't even mean anything!" and you would be right! That's the point. Because String.tag
doesn't say what it does it says what it is which is exactly what it's supposed to do. It is the "identity tag" in that it is the identity of a tag, it is the simplest possible tag that doesn't do anything but exist. It implies no function, nor does it imply a lack of function.
Furthermore, if you used it as many people likely will:
const html = String.tag;
const css = String.tag;
const sql = String.tag;
It becomes extremely clear that you are just making these identifiers string tags. This also has the advantage of being short, and even happens to be the same number of characters as String.raw
so if someone wanted to replace all incorrect uses of String.raw
with the correct new tag, this won't move a single character.
It means nothing, it does nothing. It's just there for other tags to build upon.
That being said, I think of the other proposed terms "cooked" is the one I like best purely because "raw" already exists. I also think that its lack of contextual meaning at least means it probably won't be misunderstood, the advantage of a potentially non-obvious name is that few people will assume they know what it means and will look it up. This is as opposed to "interpolate", "identity", "parsed", and similar which someone could easily misinterpret without even stopping to think "do I actually know what that means?"
String.tag
isn't bad. And running a bit further in that direction, I'd propose String.fromTemplate
:
function safePath(strings, ...subs) {
return String.fromTemplate(strings, ...subs.map(sub => encodeURIComponent(sub)));
}
let id = 'spottie?';
safePath`/cats/${ id }`;
// → "/cats/spottie%3F"
It follows the existing "$Type.from$Thing" pattern of Object.fromEntries
/Uint8Array.from{Base64,Hex}
/Array.fromAsync
, providing a clear suggestion of how to read it and what to expect it does, and makes sense independent of any comparison to String.raw
(e.g., consider let buf = String.fromTemplate(["This ", " is *cooked*!"], "string");
as basically the JS transliteration of C sprintf(buf, "This %s is *cooked*!", "string");
).
I like String.fromTemplate
, it makes a lot of sense when called directly which is likely how this method will be used most. We have String.fromCharCode()
and String.fromCodePoint()
already. What about just String.from
?
I do think it's slightly more redundant though, just as String.interpolate
implies doing something that the template tag already does, String.fromTemplate
also sort of implies that the template produces something that isn't a string and needs to be converted to one. But I like it better regardless.
String.tag
: Even though I really like its shortness, I feel this is not at all as "not doing anything" as @zeel01 said. It looks like to be something related to the tag functions itself, because actually the exemple you wrote const html = String.tag
would be completely useless, but more a utility to generate tag functions: const html = String.tag( someStringOrValueFunctionHelperForHTML )
.
The point is that this spec's function is not actually doing nothing, it concatenates an array of strings with an array of values (already resolved/interpolated outside) by alternation. I feel it will be more usefull as a utility that stands inside another tag function to get the collage result and do something with it, than used as a direct tag function...String.fromTemplate
: This one makes really sense! +1 for all explainations, but the wording is way too long... Maybe something like String.fromTpl
or String.fromLit
(for template literal)I may also purpose:
String.zip
, that is short and kinda usual name of the process to alternate values from 2 arrays:
C D E
/ / /
A1B2*. * . *
\ \
3 4
String.collage
that make sense to my french ;) but I'm unsure this is well understood worldwide.String.concat
being the static method counterpart of the instance method, but maybe not obvious about alternation of strings and values.String.alternate
but a bit long.String.add
...String.$
(dollar) resolving ${}
interpolations.String._
to avoid searching a name ^^;What about just
String.from
?
Usually a static .from
method can take anything and convert it to related class. I would imply that the input is not especially a template literal arrays but anything, so it duplicates .toString()
somehow...
String.fromTemplate
also sort of implies that the template produces something that isn't a string.
I do not agree with this point: get String from Template
is obvious about what the result will be...
String.glued
?
We generally don't see abbreviations in the language, and I think that's a good thing. While fromTemplate
might be long, it's clear. While fromTpl
is confusing, and fromLit
sounds like it's from the Lit framework.
My point about "not doing anything" is that template literals already do this, they already interpolate/merge/zip/concatenate inherently. If I write this:
const content = "Inner HTML!";
element.innerHTML = `<div>${content}</div>`;
That already does all of the functionality, that will merge the template together and product a string that will be assigned to innerHTML
.
Using this String.cooked
method doesn't change that, its entire purpose is to not actually modify the behavior of the template literal. Rather, it has two alternative purposes:
My example expanded:
const html = String.tag;
const content = "Inner HTML!";
element.innerHTML = html`<div>${content}</div>`;
Note that the html
template tag doesn't do anything that the template doesn't already do. But my IDE can detect that tag, and switch the syntax highlighting mode to HTML within the template. This is a highly desirable feature.
The other use is as the basis for tags that do actually do something, but don't modify the templating. For instance, I might implement an actual html
tag that returns a new DocumentFragment
instead of a string.
String.fromTemplate
also sort of implies that the template produces something that isn't a string.I do not agree with this point: get String from Template is obvious about what the result will be...
The result is obvious, the implication is not. A template already creates a string, the result of my first example with no tag is a string. Using a template doesn't produce some specialized object, it produces a string, as such:
String.fromTemplate`<div>${content}</div>`;
Is arguably redundant (though not as bad as many of the others). This statement doesn't create a string from a template, it actually just gives you the result of the template - hence why String.identity
is a suggestion, it is essentially a no-op.
The muddy water comes from using the method in order to build a template tag such as @gibson042 wrote:
function safePath(strings, ...subs) { return String.fromTemplate(strings, ...subs.map(sub => encodeURIComponent(sub))); }
That tag does actually modify the way that the template is interpolated. This is why the naming is so difficult, because in this use case there is a good argument for a name like String.zip
while in my previous example zip
wouldn't make sense at all.
Still, I think String.fromTemplate
is among the better options because it at least has some precedent in terms of naming convention, and the slightly wrong implication I noted earlier isn't nearly as big as the implications of String.interpolate
. Furthermore, fromTemplate
is pretty equally logical both as tag and when used to implement a tag.
Still, I think String.tag
takes a step even further, by representing "The tag function" and nothing more.
As for collage, concat, alternate, add, glued, etc. These all have the same issue as with interpolate or parsed: They only make sense in the context of implementing another tag function, they only make sense when called with the array and strings. If you do:
String.glued`<div>${content}</div>`;
...it doesn't really make much sense, and at best implies that the template requires a tag in order to function (which it does not). And if you do const html = String.glued;
it's likewise unclear what you're trying to do.
Edit: Mixing markdown and templates is... not great.
But my IDE can detect that tag, and switch the syntax highlighting mode to HTML within the template. This is a highly desirable feature.
+1 for this argument, I also use this VSCode extension, but for your information it work also with a comment:
/*html*/`<div>${content}</div>`
;)
I almost agree with all the rest of your last arguments, except I really don't see the point of using this "noop" function as a tag directly, it is the same as not using a tag... I feel this usage would be rather rare !
Even with your exemple of noop html
tag, you are already renaming it so the original name does matter most...
String.asIs
?
If this is something that we expect people to use as both a function and a tag, then the name needs to suggest that the signature is what you'd get when using it as a tag, because that signature is not what you'd normally expect for something you call as a function. Which means fromTemplate
or defaultTag
or possibly cooked
(by analogy to raw
) all work, but nothing along the lines of glued
or asIs
or concat
.
What is the point of using it as tag ? It only makes your source code bigger, and adds useless virtual machine's rountrips decreasing performance ! Everytime you would use it as a tag, you better have don't use tag for performance...
BTW the tag's signature is indeed needed to be able to pass arguments around easily :
const myTag = ( ...args )=> {
const sanitized = utils.sanitizeTemplateLiteral( ...args )
let result = String.cooked( ...sanitized )
result = result.replace( /foo/g, 'bar' )
...
}
What is the point of using it as tag?
For example:
function renderIntro(name, needsSanitation) {
let tag = needsSanitation ? sanitize : String.fromTemplate;
return tag`Hello ${name}`!
}
BTW the tag's signature is indeed needed to be able to pass arguments around easily :
No. If we expect this not to be used as a tag, then the obvious signature is to take two lists. You can just write template, ...exprs
everywhere you are writing ...args
in your example. Having to do this instead of ...args
is not worth the less-obvious signature. The only reason to use this signature is so it can be called as a tag.
function renderIntro(name, needsSanitation) {
return needsSanitation
? sanitize`Hello ${name}!`
: `Hello ${name}!`
}
function renderIntro(name, needsSanitation) {
let name = needsSanitation ? sanitize(name) : name
return `Hello ${name}`!
}
Please imagine a more complex template expression, such that you wouldn't want to repeat the whole thing or sanitize each individual expression, as was obviously my intent.
What is the point of using it as tag ? It only makes your source code bigger, and adds useless virtual machine's rountrips decreasing performance !
You would be unlikely to author code with it used directly as a tag rather than "renaming" it by assigning it to some other identifier. However picking a name under the assumption that "nobody will ever use it like that" isn't great, and at the very least there will be examples written on sites like MDN that absolutely will show it used in that way in order to demonstrate what it does/doesn't do.
And as @bakkot pointed out, whether or not anyone uses it directly as a tag, the signature of the function has to match the signature of a tag function in order for this sort of "renaming" to work in the first place. As such, the function will be a valid tag function even if using it as one directly is seemingly pointless. Though I'm not really convinced that it's truly pointless, I just haven't come up with a good reason to use it without a rename yet.
but for your information it work also with a comment
Yeah, unfortunately the comment way doesn't always work correctly and it's needlessly verbose. Plus it's a little weird to use both methods. For instance in a project that actually uses Lit, the html
tag is a real thing that does something, but you might also use SQL someplace and for consistency want a sql
tag rather than /* sql */
comment.
Another neat thing to do with these, is have a debug-only behavior. For instance:
function debugTemplate(strings, ...values) {
const string = String.cooked(strings, ...values);
console.log(string);
return string;
}
const sql = DEBUG ? debugTemplate : String.cooked;
SQL in particular I commonly need this because SQL errors are almost always totally unhelpful, and being able to copy-paste the fully interpolated query into another tool to check it over is invaluable.
@bakkot Okay for the non duplication of template, it's why I wrote a 2nd exemple... The strings parts of your template won't ever have to be sanitised, it's your source code! Only values part may have to be sanitized...
Still you can process the strings part if you want or need but the case fall back to the non tag usage because you will need the strings array therefore your function to be a tag...
@zeel01 I agree on the tag's signature as said before...
For your SQL exemple:
const query = ` blah ${table} blah ...`
DEBUG && console.log( query )
Or if a sql tag is needed to do actual jobs:
const sql = ( ...args )=> {
const query = String.{choose a name in this thread} ( ...args )
DEBUG && console.log( query )
return MySQL.exec( query )
}
const rows = sql`SELECT ...`
Examples are intentionally trivial, refactoring a trivial example doesn't really add anything to the conversation.
Imagine I have a module with 30 SQL queries in it. I don't want to assign every query to a temp variable, then log that variable, then run the query. Instead, I want to simply pass the query into the SQL library I'm using.
The tag function lets me sneak a function call in between creating the string and sending it to the query - one that doesn't add more than 3 characters for each use, and as a bonus makes the IDE switch syntax modes. As such I can write all the queries in a normal natural way, and enabling debugging is done elsewhere with only a couple of lines rather than debugging logic being mixed into every single query.
In a way, tag functions are very similar to Decorators, they essentially decorate a string rather than a class or function. They allow you to encapsulate some logic behind a simple declarative syntax, and the particulars can be worked out elsewhere.
I agree that this is a bit off topic... But what I wanted to point out is: Does this spec's function really need to used as a tag direcly? And this will drive the name choice because the name will also "force" the usage...
The point is that whether or not anyone will actually use it like:
const myString = String.cooked`Some ${templated} string`;
The signature of the function must be that of a template tag for some of the most desirable use cases. That means it is a valid tag, and as such should make sense of for some reason someone did use it as one. And at the same time, it needs to make sense when delegated to within a custom tag, whether the custom tag is just getting the cooked string and doing something else with it, or actually modifying the interpolation behavior.
That is, regardless of how the function is used, it should not have a confusing name.
The strings parts of your template won't ever have to be sanitised, it's your source code! Only values part may have to be sanitized...
The appropriate way to sanitize each part may depend on the context of the string. You can't generally take something which operates on a template literal and transform it into something which operates only on a single string - the surrounding context is an important part of the logic. (Consider sanitizing HTML, for example: the rules for escaping attribute values are not the same as the rules for escaping element contents.) Sanitizing each part individually, without the surrounding context, isn't generally possible.
So, yes, the function does in fact really need to be used as a tag directly.
@zeel01 has you said earlier, the difficult task is to concile 2 usages that are so different... It is why I argued on the side: It wont be used as a tag so the name should reflect that...
That being said, I don't want to spam this thread anymore, I apologize for this BTW ! Other contributors will argue, I may change my mind later :)
A last philosophic comment:
We, as developpers, and here, as specifiers are responsible of the consequences of choices we take. More that final users who will use what is available.
If the simple choice of naming a future fonction would lead to a usage that kills performances for a matter of lazyness, we would have be responsible of more worldwide energy comsomption therefore more CO2 emissions in a world where global climate change matters, and where sobriety should be the path to take...
I would hope bundlers/optimizers would remove completely no-op uses. Heck, I would hope that runtimes would be smart and realize a tag doesn't do anything and just not call it. I wouldn't worry about a hypothetical extra function call, there are many ways to avoid any runtime performance penalty without the developer needing to jump through as many hoops authoring the code.
Splitting this out from #1 cause this tends to be a big topic.
options:
String.cooked
The primary appeal of this is to contrast it against
String.raw
. A potential downside to that name is that it doesn't really match the intended use case of this function: To make it easier to implement "value pre-processing" tagged template functions.String.interpolate
This function aligns a bit more with the intended usage of this function, and is overall a bit more literal about what it does internally.
String.identity
This name has been brought up before, it refers to the fact that these two are equivalent:
The downside is that using this function as the direct tag of a tagged template is not really its intended use case, and calling it within another tag makes it a somewhat weird name.
Other names
Feel free to suggest other names, but if you do, please explain what that name accomplishes and what it loses out on from the other names already suggested.