Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Name bikeshed #5

Open
jamiebuilds opened this issue Aug 31, 2021 · 55 comments
Open

Name bikeshed #5

jamiebuilds opened this issue Aug 31, 2021 · 55 comments

Comments

@jamiebuilds
Copy link
Member

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:

let a = String.identity`tag`
let b = `tag`

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.

@bakkot
Copy link

bakkot commented Sep 1, 2021

+1 for String.cooked.

On reflection, I also like String.interpolate. Sorry to be unhelpful!

@jamiebuilds jamiebuilds pinned this issue Sep 2, 2021
@tabatkins
Copy link

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:

  • The obvious connection between "raw" and "cooked" suggests they're doing nearly the same thing, which is correct.
  • The meaning of "cooked" hints toward the transformation of the literal segments into their final (escapes resolved) form; the word "cooked" is used in this way in other programming contexts sometimes.

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.

@michaelficarra
Copy link
Member

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.

@bakkot
Copy link

bakkot commented Sep 9, 2021

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.

@tabatkins
Copy link

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.

@jamiebuilds
Copy link
Member Author

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.

@jamiebuilds
Copy link
Member Author

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: "",
})

@rbuckton
Copy link

"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.

@senocular
Copy link

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" }

@GreLI
Copy link

GreLI commented Oct 27, 2021

Could it be String.parsed? Just like JS engine parses template literals. For me 'cooked' feels like a synonym for 'parsed'.

@bathos
Copy link
Collaborator

bathos commented Oct 28, 2021

@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".

@jamiebuilds
Copy link
Member Author

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.

@bakkot
Copy link

bakkot commented Nov 1, 2021

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.)

@bakkot
Copy link

bakkot commented Nov 1, 2021

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.

@benatkin
Copy link

How about pasteurized?

@Jbonez87
Copy link

I like String.interpolate the most.

@benatkin
Copy link

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.

@ptomato
Copy link

ptomato commented Apr 17, 2023

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.

@benatkin
Copy link

benatkin commented Apr 17, 2023

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. 🤔

@bathos
Copy link
Collaborator

bathos commented Apr 17, 2023

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.

@benatkin
Copy link

benatkin commented Apr 17, 2023

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.

@bergus
Copy link

bergus commented Jul 26, 2024

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.

@devingfx
Copy link

devingfx commented Sep 27, 2024

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

@ljharb
Copy link
Member

ljharb commented Sep 27, 2024

merge sounds very similar to me as concat, which is basically just +.

@devingfx
Copy link

devingfx commented Sep 27, 2024

merge sounds very similar to me as concat, which is basically just +.

Yes exaclty !
Which is basically what this method is doing:

String.merge`Hello ${who} !` === 'Hello ' + who + ' !'

@zeel01
Copy link

zeel01 commented Oct 4, 2024

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?"

@gibson042
Copy link

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");).

@zeel01
Copy link

zeel01 commented Oct 4, 2024

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.

@devingfx
Copy link

devingfx commented Oct 7, 2024

  • 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 ^^;

@devingfx
Copy link

devingfx commented Oct 7, 2024

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...

@devingfx
Copy link

devingfx commented Oct 7, 2024

String.glued ?

@zeel01
Copy link

zeel01 commented Oct 7, 2024

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:

  • Allow for the creation of tags with no runtime implementation that just provide a hint for the editor.
  • Streamline the creation of tags that add functionality on top of templates, and don't modify how they are interpolated.

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.

@devingfx
Copy link

devingfx commented Oct 7, 2024

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...

@devingfx
Copy link

devingfx commented Oct 7, 2024

String.asIs ?

@bakkot
Copy link

bakkot commented Oct 7, 2024

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.

@devingfx
Copy link

devingfx commented Oct 7, 2024

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' )
    ...
}

@bakkot
Copy link

bakkot commented Oct 7, 2024

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.

@devingfx
Copy link

devingfx commented Oct 7, 2024

function renderIntro(name, needsSanitation) {
  return needsSanitation
    ? sanitize`Hello ${name}!` 
    : `Hello ${name}!`
}
function renderIntro(name, needsSanitation) {
  let name = needsSanitation ? sanitize(name) : name
  return `Hello ${name}`!
}

@bakkot
Copy link

bakkot commented Oct 7, 2024

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.

@zeel01
Copy link

zeel01 commented Oct 7, 2024

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.

@devingfx
Copy link

devingfx commented Oct 7, 2024

@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...

@devingfx
Copy link

devingfx commented Oct 7, 2024

@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 ...`

@zeel01
Copy link

zeel01 commented Oct 7, 2024

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.

@zeel01
Copy link

zeel01 commented Oct 7, 2024

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.

@devingfx
Copy link

devingfx commented Oct 7, 2024

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...

@zeel01
Copy link

zeel01 commented Oct 7, 2024

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.

@bakkot
Copy link

bakkot commented Oct 7, 2024

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.

@devingfx
Copy link

devingfx commented Oct 7, 2024

@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 :)

@devingfx
Copy link

devingfx commented Oct 7, 2024

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...

@zeel01
Copy link

zeel01 commented Oct 7, 2024

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.

@bakkot
Copy link

bakkot commented Oct 7, 2024

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.

Unfortunately this optimism isn't justified in the current ecosystem. Very few people are running tools which will eliminate this sort of thing before shipping to production. And engines can't avoid the call because they can't know for sure that String.fromTemplate will still be the function in question.

But the overhead of this is very small. Just like anything else, allowing developers to more directly express their intent is often worth the cost of this tiny amount of overhead. And there's not a better way of writing my example snippet above: even if you were willing to duplicate the whole template, that extra code has real costs as well.

@devingfx
Copy link

devingfx commented Oct 7, 2024

@zeel01 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.

Nope! It make sense to use a name that goes in both usage, if it make sense to use it at both usage... You know that any function that takes a single string can be used as a tag right? (because an array of 1 string .toString gives this string back)

const sym = Symbol`foo`
const num = Number`42`
const elem = document.createElement`div`
const items = document.querySelectorAll`#my-list > li`
const $ = document.querySelectorAll   // $`...`

So if this spec's function as the tag signature, this is not obvious it as to reflect this usage. The fact that somebody may use it like that is not an argument for its naming. It makes sense only if we name it to be so...

@bakkot Consider sanitizing HTML, for example: the rules for escaping attribute values are not the same as the rules for escaping element contents.

there's not a better way of writing my example snippet above:

function renderIntro(name, classes, needsSanitation) {
  let { attr, text } = sanitizers.forHTML
  if( !needsSanitation )
    attr = text = o=>o
  return `<div class="greetings ${attr(classes)}">Hello ${text(name)} !</div>`
}

There is no more a need for a general sanitizer that have to parse the context of string to determine if it is an attribute or a text element (so less instructions, memory used etc), the template is not duplicated (no memory waste), the noop function is used (and defined in memory) only if condition tells so...

@bakkot
Copy link

bakkot commented Oct 7, 2024

There is no more a need for a general sanitizer that have to parse the context of string to determine if it is an attribute or a text element

That requires discipline on the part of every user of the sanitization APIs to confirm that they are used in the correct context every time. The whole point of tagged template literals - literally the reason they exist - is to allow you to build sanitization and similar APIs which can take into account the surrounding context, so that the author of the tag can exercise that discipline instead of requiring every single user to get it right in every single instance.

@devingfx
Copy link

devingfx commented Oct 7, 2024

// framework code
const html = (...args)=> (new DOMParser).parseFromString( String.cooked(...args), 'text/html' )   // not a tag usage
const sanitizedHtml = (...args)=> html( ...sanitizer(...args) )  // sanitizer has still a tag signature

// userland code
function renderIntro(name, classes, needsSanitation) {
  let tag = needsSanitation ? sanitizedHtml : html
  return tag`<div class="greetings ${classes}">Hello ${name} !</div>`
}

PS: String.cooked or whatever its name, will need to work with both signatures cooked( string ) and cooked( string[], ...vars ) that be acheived easily with:

String.cooked = ( oneOrMoreStrings, ...props )=>
    [].concat( oneOrMoreStrings )
        .map( (s,i)=> s+( i in props ? props[i] : '') )
        .join('')

@bakkot
Copy link

bakkot commented Oct 7, 2024

Yes, of course it is possible to wrap any signature so that the result is usable as a tag, and thereby not need to call it as a tag directly. That does not mean that it would not be useful to be able to call this function as a tag directly.

I don't think this line of inquiry is useful so I'm going to stop engaging with it now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests