-
Notifications
You must be signed in to change notification settings - Fork 75
tagged template literals should be allowed (for completeness) #54
Comments
I'll start it off with a reason TO support tagged template literals. I'll be honest; I was struggling, at first, to think of a real-world use case (other than code-golf, which is actually a thing that happens in the real world). However, I just thought of one possibility. Suppose in the future (you want this feature to be as future-proof as possible, right?) there is far less need to transpile ES6 syntax to ES5 (i.e., Babel) as a greater and greater % of browsers in use provide native support. (Aside: Out of curiosity, here is the current state of support for template literals.) In order to provide minifiers (such as UglifyJS) with all tools at their disposal to save bytes over the wire in your |
Adding optional tagged template literal support is more spec text, although it is easy to do. Therefore, I’ve chosen to drop them by default, i.e. unless/until there is consensus for adding them. In other words, in absence of a good reason for one way or the other, I’ve picked the laziest option for the spec writer and (I expect) the implementors. |
I see; I guess I'm not too familiar with the process yet. Would it be a reasonable request to suggest that any explainers/spec text clarify that template literal support is not required but is recommended for completeness's sake?
I'd hope the example I gave, while perhaps not the most compelling of reasons, at least nudges implementations to include support for tagged template literals. |
I do fear, however, that if template literal support is not required in the spec that browser/Node.js, etc. implementations would become fragmented, only complicating things; for example, it would lead caniuse.com to cover three different features:
This would inevitably delay or prevent minifiers' ability to make use of their combining with optional chaining as in comment. I'd probably be willing to accept that risk if it's at least strongly suggested to include template literal support, however. Edit: If anyone else can think of other use cases, feel free to jump in. |
@ScottRudiger I might be wrong, but to my knowledge, none of the JS spec is considered "recommended"; a feature is either in the language (meaning all JS engines will implement it) or not. For the reason you described, I don't think that type of thing would make sense in JS. It's not just tagged template literals, there are lots of features that might be included here for the sake of completeness, like optional The way I think about this is that it's best to work incrementally, just like with code. A less ambitious proposal is inherently less controversial, so it'll make it through committee faster. Optional tagged template literals can always be added later. That's also why nullish coalescing is it's own proposal instead of being included here, even though the two are related. The case to be careful about, though, is when a proposal limits what changes can be done in the future. That's why so much thought has been put into avoiding the syntax clash between optional chaining and nullish coalescing, even though the two proposals are separate. Optional chaining has been in stage 1 for over a year, despite significant work and despite it being very popular. For me, it's a bit frustrating watching it take so long, although it's certainly understandable that people want to get it right. There's already enough detail and controversy in the core feature that IMO it's best to keep it as simple as possible and leave any nice-to-have extensions for future proposals. |
I was imagining that to be the case. That's why I was thinking a benefit of requiring support would be to prevent different implementations supporting/not supporting the feature--a problem that seems to have contributed much complexity to JS in the past (for library writers/web devs/JS users in general).
I can definitely see your point regarding optional property assignment, as that is a separate feature that could be, and likely will be, discussed and separately introduced in another proposal down the line. However, I don't see tagged template literal support as necessarily a full-fledged feature on its own. It's more on the same level as optional function/method invocation, e.g.,
Thanks for the insight around the process for getting proposals through committee. I was surprised to see that @claudepache originally wrote this proposal over two years ago. As an aside, I'd like to commend the TC39 contributor/proposal writers as well as the TC39 members for their patience and persistence in trying to improve and extend JavaScript, with substantial time and effort on their parts.
I totally agree; that's why I've been kind of obsessed with following and commenting in #51, #34, and #5, as I think it's very important to settle on a reasonable syntax for many reasons, including future considerations. (I'd encourage anyone reading to see those issues for discussion on syntax. Leave this thread to the issue at hand and I'll promise not to reiterate my preferred option here. 😉) To address your point though, I don't think that requiring support for tagged template literals now does anything to limit future proposals, other than negating any need to introduce a proposal just to specifically add support in the future. Conversely, I'd think my previous comment shows that requiring support now may actually save all involved from potential complexity and heartache if/when implementations diverge. 💔 I'm open to hearing what foreseeable changes, if any, could be limited by template literal support now, however. Let me know. Thanks for your well-thought out reply. |
I like @claudepache 's minimalism approach here: It seems less likely to lead to confusion. I don't think it's possible for us to create a fully "orthogonal" optional chaining feature here--there are lots of situations where you might want short-circuiting or optional chaining that would be pretty infeasible or excessively complicated. Ultimately, programmers will have to learn what's allowed. |
At first sight, optional tagged template literal is tempting ”for completeness”. But on reflection, I think that: a ?. `${x}` // undefined if a is nullish, a `${x}` otherwise is about as reasonable or useful as: a ?.* b // undefined if a is nullish, a * b otherwise Also, one may also make sense of template literal optionally tagged, i.e. (random strawman syntax): a???`${x}`// `${x}` if a is nullish, a `${x}` otherwise |
To be precise, ”Optional tagged template literal support” may be subdivided in two cases (although I don’t think that it is a good idea to support one and not the other):
|
I think the former must be supported; the latter i think is a totally separate feature. In other words, when deciding what to support, i think there shouldn’t be anything we don’t support inside an optional chain - only things at the beginning of an optional chain (ie, the things that are optional) |
I totally concur with this line of thinking. To me it seems like if the former (
However, I disagree--to a degree--with this. I think I already mentioned this (not quite awake yet), but |
It depends whether a template literal found at the end of an optional chain is considered to be inside it or after it. Surely, for a spec guru it is inside, but for a lambda programmer? This is not really a issue if the application of the incorrect thinking would necessarily lead to a TypeError (and this is why I consider that short-circuiting up to the end of the chain is safe even for those that do not understand it). But regarding template literals, someone could think of: a?.b`c` as an optionally tagged template literal: (a == null ? `c` : a.b`c`) (Personally, I don’t think it is a real problem, but it is worth raising the question.)
I disagree. For someone not versed in the spec details, it would seem like an arbitrary restriction or an inconsistency if some construct is allowed in some location of an optional chain, but not in another. (Yes, I’ve changed my mind since I’ve written #4 (comment).) |
👍 Totally agreed; this is what I've been trying to say, but in better, more concise words than I can ever dream to think of 🤣. I'd also add: it seems like an arbitrary restriction or inconsistency if some construct is allowed in a <regular> chain, but not in an <optional> chain.
But I honestly don't see how they could think of that meaning if they have an understanding of either optional chaining OR tagged template literals (read the docs). If they wanted to achieve a?.b.`c` ?? `c` edit: or even without knowing about the null coalescing operator's existence: a?.b.`c` || `c` |
I think debating whether "invocation" and "tagging a template literal" are the same is separate; I'm still grouping things into "optional X" and "X behind an optional chain" - I think that everything should work in the "X behind an optional chain" bucket identically to how it would work with "X behind a normal chain" (when X is not nullish, obviously), and I think that we can and should have distinct discussions about supporting "optional X" for any given X's. |
It’s not the first operation of the chain that’s optional, it’s the chain as a whole. (This is how short-circuiting works without resorting to artefact like Nil reference or abrupt completion.) |
@claudepache yes, but the pivot point of the optionality is where i'm focusing on as the dividing line. |
Yes, but I think I place that pivot point more precisely than you, i.e. more precisely than the formal grammar. In an expression like: a??.b.c I conceptually put the dividing line between the |
Ah - I'm putting it between
the act of tagging the template literal, even though it happens conditionally, isn't optional - it's required (if the chain continues). |
In |
FWIW, I would intuit
to mean
and would be very surprised if that were not to work. |
The example proposed in Comment #0 is not a valid use case, in the sense that neither I haven’t enough imagination in order to find even a semi-valid use case for optional tagged literal, and I expect that it would be a tough exercise. Anyway, I stand on my position, that we should either reject both ”tagged literal in optional chain” ( obj?.optionalTag `c` It seems to work at first sight..., but the programmer realises soon their mistake, that they really mean: obj.optionalTag?.`c` It would appear more inconsistent that the first expression works and the second expression doesn’t, than the two expressions are equally rejected (or equally accepted). Or, from another perspective, in the remote case we find some good reason for writing |
Here is a reason why I think we should reject tagged template in optional chain: A tag resembles more to a unary operator (as a?.b
`c` appears as if it should desugar to: (a == null ? undefined : a.b)
`c` Granted, this is completely useless, because undefined
`c` is always a TypeError. But maybe in some future we could find reasonable to treat |
Maybe I'm not following along, but I'd think that if template literals are allowed in/after an optional chain, ASI wouldn't apply à la #56, and a?.b
`c` would be interpreted as a == null ? undefined : a.b`c` Therefore, as written, there would be no chance for the result to be undefined
`c` because the whole optional chain would either short-circuit to I agree both obj?.optionalTag `c` and obj.optionalTag?.`c` should be allowed for consistency, rather than one or the other. |
The ASI issue is independent from this discussion. Whatever the fate of template literals, we will prevent ASI from producing astonishing and/or forward-incompatible outcome.
The answer is ”yes/no”, as the question contains ”in/after”... More precisely, given
|
I'd like to suggest that I have a use-case for including support of both: a?.`b`
// and
a?.b`c` I have been working on a type linter/checker for JS, as an alternative to TS/Flow, which uses template tags as annotations for value/expression types. It looks (in part) like this: var x = string`${ someval }`; This code is (optionally) checked at compile time, and if it can be verified statically, it's simplified to ship this for runtime code: var x = someval; But if it cannot be verified at compile time, then the tag funcion is left in, and serves as a runtime assertion. There's a runtime library that provides a global identifier If for some reason, you fail to include the runtime library, then It may be preferable for some users of my type linter that it convert the code to this before shipping to the runtime (this would be a configuration option for the tool): var x = string?.`${ someval }`; That way, if the runtime library is available, you get the assertion checking. But if the runtime library is missing, the tag call doesn't happen and throw the type-error. Another case where they may have tag function calls in their runtime, but not have the tag functions defined, is in the case of user-defined types: var x = mycustomtype?.`${ someval }`; As to what semantics that would imply if the tag function is nullish, there are two options for what that statement would effectively produce in equivalent runtime behavior: // option 1:
var x = `${ someval }`; // no tag
// option 2:
var x = undefined; // no template expression at all If option 1 prevails, that's fine because you basically just get a Either way that semantic plays out, I think the desire to avoid a type-error on the missing tag function call could very well be the desired behavior for some of my tool's users. It's also entirely plausible that some users of my tool may prefer to have these type-annotation tags be namespaced, like: var x = types.string`${ someval }`; So for the same reasoning as above, these users may prefer to configure the compiler to ship: var x = types?.string`${ someval }`; Or even: var x = types?.string?.`${ someval }`; Of course, I won't offer this config option in my tool if TC39 decides to not support these syntaxes. But I think these syntaxes should be supported, and I think this a use-case for why their support could be useful to some users. |
This comment has been minimized.
This comment has been minimized.
@ljharb I was believing/assuming that |
So, if I understand your use case, you want null ?. `foo` to be equivalent to: `foo` However, this is not what optional chaining does (that is to say: would do if template literals were allowed in that position). Indeed, given I see two ways to address your use case, none of them is related to optional chaining:
var x = myPossiblyNullCustomType `${ someval }`
var x = (myPossiblyNullCustomType ?? String.cooked) `${ someval }` or: if (myCustomType == null)
myCustomtype = String.cooked
var x = myCustomtype `${ someval }` Such a tag has already been proposed on esdiscuss. |
Nope. I called out in the middle of my message that the semantics could debatably be either of these: `foo`
// or
undefined But that personally I'd prefer the However, whichever way that semantic played out, I could see users of my tool wanting that optionality (aka, graceful non-error-throwing handling) instead of just a type error. |
Unless I've missed something, I don't think optional chaining does that at all, yet. Wasn't that the point of this thread, to debate if It's currently listed as unsupported, and my message was to argue a use-case for why it should be supported. |
For sure, I meant: it is what optional chaining would do if template literals were allowed in that position. I’ll edit and add the conditional in my post. The semantics of optional chaining is: Evaluate the LHS; if the value of the LHS is nullish, stop evaluating the current expression and yield undefined; otherwise continue evaluating the current expression normally. — All that is orthogonal to the “support” of template literals. |
I'm fine with that semantic, and I'm fine that template literals, if supported, would not have any special case handling in that respect. I just want to re-emphasize that in my tool's use case, I can see why some would prefer the optional-tag-call resulting in silent/graceful It would be helpful to have both |
I’ve re-read the use case you posted above. Unless I’ve missed something, given: var x = types.string`${ someval }`; // decorated with some “optional” mark(s) and supposing that var x = types?.string`${ someval }` ?? someval which goes against DRY. A better alternative (in my opinion) is: var x = (types?.string ?? String.cooked)`${ someval }` which does not use template literal in the optional chain. Or do you have another concrete example, that shows that the “produce undefined if tag is nullish” behaviour is indeed useful? |
I am not sure that's the case. I think some might, but others might not. Some might want it, but be surprised by what they get and only realize later that this wasn't what they really wanted after all. But regardless of whether they "want" that or not, that's not the semantic that would be documented as being provided. So it's moot with respect to our discussion here.
Yes, this is absolutely true, but that's actually a great argument why it should be By instead getting an In other words, my claim is that at least some of my tool's users will prefer to detect/handle this problem scenario more silently/gracefully via the If TC39 were to still decide not to support the // var x = typeof string == "function" ? string`${ somevalue }` : undefined;
// edit:
var x = string != null ? string`${ somevalue }` : undefined; ...which is exactly the proposed semantic of this feature. So of course, in this case, I'd far rather have it produce and ship: var x = string?.`${ somevalue }`; That's the main crux of my use-case argument in favor of this feature. Moreover, I would claim that the existing spec'd behavior -- IOW, if this is useful/OK to expect this of usages of I don't see any compelling reason to favor/distinguish that |
Based on optional call's semantics, it would not, in fact, check typeof function - it would check something more like |
@ljharb you're right, the check is (and should be) a nullish check. I was trying to be more descriptive about my thinking, but that came at the cost of precision. |
In the README of this repo, I’ve given two concrete examples for optional method calls. In the first one, no code is needed to handle the “problem”, because the problem is completely solved by the optional call. In the second example, nothing additional is needed in order to have “graceful” degradation—although in other similar cases, a simple
Until now, there haven’t been convincing use cases for optional tagged templates. Your use case might be the first one, but it has still too many “might”s for my taste. Also, what you call consistency here is what I call completeness. Probably the reason is that I consider that “tagging a template” is not the same thing as “invoking a method”, even if both end up executing some function. (Other example of “(not) the same thing”: “constructing a class instance” (with |
OK, fair enough, those two specific kinds of cases don't require the special handling. But certainly
I don't object to that characterization.
Though I personally don't care about (or often use) |
@getify Sorry I don't very understand the case you provide. If |
@hax I do not think it will lead to errors, I think it will prevent them. As I said earlier, the chances that a tagged template being undefined, and then needing basic checks to see it's not a string/etc as expected to handle the error, are absolutely identical to the checks and handling you would have to do for any function call (or property access, for that matter) which was short-circuited to undefined. If we can imagine those checks and handling for functions and properties, then we should be able to see that those patterns apply equally to tagged templates. |
var obj = {};
var a = obj.?numberOne;
// safety guard in case of short-circuiting
if (typeof a == "number") {
console.log(a + 41);
}
var b = obj?.fortyOne.?();
// safety guard in case of short-circuiting
if (typeof b == "number") {
console.log(b + 1);
}
// if we do the other two, we should do this one too
var c = obj?.toNum?.`41`;
// safety guard in case of short-circuiting
if (typeof c =="number") {
console.log(c + 1);
} |
If you want to convince us, you should find a concrete scenario (or, better, a real-world use case) where an optional tagged template literal is useful and better than other alternatives. I am convinced that maybe it could happen that it might be useful someday to someone. But I am not at all convinced that a random occurrence of Mere analogy to optional call is not sufficient. As a precedent, per #17, optional call has significant practical uses but optional construction has zero practical use. |
I am not justifying the request based on that. I gave the use-case as justification for the request. (see below) I am defending against the "won't this create worse errors" dismissal, which is misguided. As I've asserted and demonstrated, the "oops this is 'undefined' instead of the value I wanted" handling is identical with property accesses, function calls, and tagged template calls, which is to say you of course are going to in general have to do that for all 3 cases. Just because you refer to a few narrow use-cases cited which wouldn't need that extra handling does not justify your broader position -- or what I'm inferring is your position -- that tagged templates are of a different category, and thus don't hold weight here. This is a feature you're adding to JS, the most widely used language on the planet, and that means people are going to use it for all kinds of general coding, not just a couple of specific scenarios you cite. That means they're going to use them with functions which return values, and if the short-circuting to They'll use
I already gave you a concrete scenario. The type-annotation library (called TypL) that I am building, uses template tags for annotating the types of literals... these tags both perform type assertions as well as value-type coercions. These tags are read by a compiler (if you choose) to do compile-time type linting (like TS), but the tags can also be used in the runtime code for dynamic value-type assertions. The use-case again is: var x = number?.`42`; // **** if the operator is extended for template tags
if (typeof x == "number") {
// do something with the number
}
else {
// handle the lack of a number gracefully
} If try {
var x = number`42`; // if the operator is **not** extended for template tags
// do something with the number
}
catch (err) {
// handle the lack of a number gracefully
} The latter of these approaches, requiring The point, again, is that I and other users of TypL would like to be able to take advantage of Of course, there's no real code to point to doing this yet, since (1) this feature is only stage3 (2) the requested form is not supported (3) TypL is still under development. But if the requested form is added, I will add this support to TypL and then there will be real usages of the pattern. |
fwiw the alternative would probably be closer to |
That's one way of doing it, but I would say it completely breaks the ergonomic essence of this tag acting like a value-site type annotation, so it isn't how I would want to do it, nor do I think anyone who I expect to use TypL. But your point of optionality is valid with respect to a default using the new var x = number?.`${ someVal }` ?? 42; In many cases, that sort of usage would probably be far preferable to either the |
Other examples besides TypL... both Styled-Components and Lit-HTML use template tags. And the same "what if the tag function isn't present" scenario, along with a backup default value in that case, applies to them. Could be that the whole library didn't load, or could be that a custom style type being referenced that for some reason didn't get defined. For example: const Button = styled.Button?.`
color: red;
` ?? defaultButton; Here, if With the const Button = html?.`<button> ... </button>` ?? defaultButton; |
"Forgot to load the library" doesn't feel like a strong argument because in those cases number?.`42` would be a
This feels like a really narrow use case to me. |
Agreed. I think we're going to need a really strong usecase for the committee to agree to this, but even if we find one this should be done as a follow on proposal (the core |
(edit: removed comments) I'm going to drop out of this thread because there doesn't appear to be anything further to discuss. |
Now that we are at Stage 3, we can consider that the set of included operators is fixed. Optional template literal is not included, but the spec is forward compatible with an eventual future inclusion in case the need would be manifest. |
From the current README:
For this issue I'd like to understand:
For discussion purposes, I'll provide a somewhat silly & simple toy problem-esque example:
Now, you might think that code-golf is stupid (and I'd of course avoid using this pattern in production code--or anywhere you'd like your code to be readable and easily understood for that matter); but the fact is: tagged template literals are valid syntax in JavaScript.
Therefore, if you can write
i.split` `
I'd also expect you to be able to writei.split?.` `
for the sake of completeness. Hopefully you can see past the ridiculousness of the example to see the reasoning for this.So I'd like hear some logical argument around the two questions asked in the beginning. What are the reasons to arbitrarily (seemingly) disallow use of tagged template literals in conjunction with optional chaining? If there are, technically, no sound reasons for not supporting tagged template literals, why not allow them just like they're already allowed elsewhere?
The text was updated successfully, but these errors were encountered: