I feel your pain!  We walked many of these steps, for many of the same reasons.  Each initially-promising approach (subtyping, poly expressions, target typing) turned out to have more drawbacks than benefits.  But I agree it would be nice if one could just use a string literal where a string template is needed -- no one would be confused (when it works right.)

On 3/19/2024 8:55 AM, Tagir Valeev wrote:
Hello!

Thank you for splitting the thread. I think that String is a StringTemplate in the same sense as zero is also a number, identity is also a function, and empty set is also a set. A degenerate case is important for generalization, as you don't have to think about it when it actually appears.

That said, now I have started to doubt this idea. So far I advocated for 'string should be string template', but what I really want is that 'string literal should be string template', which, while similar, is not the same. Indeed, for unification we don't actually need to support non-literal strings. It would be interesting to create a subclass of String like StringLiteral, which is constructed only from literals and implements StringTemplate. However, it will be a huge compatibility disaster. Now, my thought goes into some kind of implicit conversion from String literal (and only from literal) to StringTemplate, which was already discussed elsewhere, so the discussion is already far ahead of my thoughts :-)

To conclude, what I really wanted is a uniform way to specify StringTemplate with 0 embedded expressions and StringTemplate with 1+ embedded expressions. E.g., if we require a prefix for all string templates like ST"...", then this desire will be satisfied. If we don't introduce the prefix but can use String literal in every context where StringTemplate literal is possible (like it's in the current preview), then my desire is also satisfied. I see the drawbacks in every solution, so for now I don't have a strong preference.

With best regards,
Tagir Valeev.


On Tue, Mar 12, 2024 at 6:32 PM Brian Goetz <[email protected]> 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.


Reply via email to