Hi Brian.
I believe this is ultimately a bad idea. Note that I’ve been a strong
supporter of this position in the past.
Now, onto the reason I think it’s a bad idea. Let’s ignore legacy API
for now. Let’s assume the world is already moved on and adopted
StringTemplates. A new library that needs to parse strings which contain
sensitive user-defined values, already got the memo, and will provide a
StringTemplate-accepting factory (not merely a String- accepting one).
This world is inherently safer than the world we have today - because a
string template is typically composed of two facets:
* a “variable part” (the template arguments)
* a “constant part” (the template fragments)
Libraries should focus their validation/escaping efforts on the variable
part of a string template. But, for this assumption to hold water, we
need to be able to guarantee that the user cannot accidentally sneak in
some “variable parts” into the “constant part” of a string template.
Unfortunately, the String <: StringTemplate approach seems to allow
exactly that:
|Foo of(StringTemplate) { ... } Foo.of("Hello!") // ok, string is a
constant Foo.of("Hello" + world) // what? |
If messy, concatenated strings can be treated as degenerate templates, I
believe we’d be no better off than we are today - even in the case of
brand new API that fully bought into the idea of StringTemplate.
Maurizio
On 12/03/2024 17:32, Brian Goetz wrote:
Splitting off into a separate thread.
I would like to redirect this discussion from the mechanical
challenges and consequences to the goals and semantics.
If we are considering "String extends StringTemplate", we are making a
semantic statement that a String *is-a* StringTemplate. While I can
imagine convincing oneself that this is true "if you look at it
right", this sets off all my "self-justification" detectors.
So, I recommend we step back and examine why we think this is a good
idea before we descend into the mechanics. My suspicion is that this
is motivated by "I want to be able to automatically use String where a
StringTemplate is desired", and that this seems a clever-enough hack
to get there. (I think we probably also need to drill further, into
"why do we think it is important to be able to use String where
StringTemplate is desired", and I suspect further that part of it will
be "but the APIs are not yet fully equilibrated" (which would be a
truly bad reason to give String a new supertype.))
On 3/12/2024 1:24 PM, Tagir Valeev wrote:
Hello, Maurizio!
Thank you for the detailed explanation!
On Mon, Mar 11, 2024 at 1:16 PM Maurizio Cimadamore
<[email protected]> wrote:
Hi all,
we tried mainly three approaches to allow smoother interop
between strings and string templates: (a) make String a subclass
of StringTemplate. Or (b) make constant strings bs /convertible/
to string templates. Or, (c) use target-typing. All these
approaches have some issues, discussed below.
The first approach is slightly simpler, because it can be
achieved entirely outside of the Java language. Unfortunately,
adding “String implements StringTemplate” adds overload
ambiguities in cases such as this:
|format(StringTemplate) // 1 format(String, Object...) // 2 |
This is actually a very important case, as we predice that
StringTemplate will serve as a great replacement for methods out
there accepting a string/Object… pack.
Unfortunatly, if String <: StringTemplate, this means that
calling format with a string literal will resolve to (1), not (2)
as before. The problem here is that (2) is not even applicable
during the two overload resolution phases (which is only allowed
to use subtyping and conversions, respectively), as it is a
varargs method. Because of this, (1) will now take the
precedence, as that’s not varargs. While for String::format this
is probably harmless, changing results of overload selection is
something that should be done with care (esp. if different
overloads have different return types), as it could lead to
source compatibility issues.
I would still like to advocate for String <: StringTemplate solution.
I think that the overloading is not a big problem. Simply making
String implements StringTemplate will not break any of existing code
because there are no APIs yet that accept the StringTemplate
instance. The problem may appear only when an API author actually
adds such an overload and does this in an incompatible way with an
existing String overload. This would be an extremely bad design
choice, and the blame goes to the API author. You've correctly
mentioned that for String::format this is harmless because the API is
well-designed. We may suggest in StringTemplate documentation that
the API designers should provide the same behavior for foo(String)
and foo(StringTemplate) when they add an overload.
I must say that we already had an experience of introducing new
interfaces in the hierarchy of widely-used library classes. Closable
got AutoClosable parent, StringBuilder became comparable, and so on.
So far, the compatibility issues introduced were tolerable. Well,
probably I'm missing something but we have preview rounds just for
this purpose: to find out the disadvantages of the approach.
On top of these issues, making all strings be string templates
has the disadvantage of also considering “messy” strings obtained
via concatenation of non-constant values string templates too,
which seems bad.
I think that most of the APIs will still provide String overload.
E.g., for preparing an SQL statement, it's a perfectly reasonable
scenario to have a constant string as the input. So
prepareStatement(String) will stay along with
prepareStatement(StringTemplate). And people will still be able to
use concatenation. I don't think that the absence of String <:
StringTemplate relation will protect anybody from using the
concatenation. On the other hand, if String actually implements
StringTemplate, it will be a very simple static analysis rule to warn
if the concatenation occurs in this context. If the expected type for
concatenation is StringTemplate, then something is definitely wrong.
Without 'String implements StringTemplate', one will not be able to
write a concatenation directly in StringTemplate context. Instead,
String-accepting overload will be used, and the expected type will be
String, so static analyzer will have to guess whether it's dangerous
to use the concatenation here. In short, I think that it's actually
an advantage: we have an additional hint here that concatenation is
undesired. Even compilation warning could be possible to implement.
So, I don't see these points as real disadvantages. I definitely like
this approach much more than adding any kind of implicit conversion
or another literal syntax, which would complicate the specification
much more.
With best regards,
Tagir Valeev.