Update on String Templates (JEP 459)

Clement Cherlin ccherlin at gmail.com
Mon Mar 11 13:54:46 UTC 2024


On Sun, Mar 10, 2024 at 3:42 PM Attila Kelemen
<attila.kelemen85 at gmail.com> wrote:
>
> If the string processing burden is now pushed to the consumer API side, then wouldn't it be worthwhile to make `StringTemplate` simpler given that this means a lot more people are forced to implement processors? I mean that having two lists where you have to alternate between the two is rather unintuitive which is proven by the fact that it forces `StringTemplate` to do the empty string hacks to support alternating between the two lists.
>
> Given that we have these nice pattern matching syntaxes, wouldn't it be much nicer to make `StringTemplate` to be a simple wrapper for a `List<StringTemplate.Part>`, where `StringTemplate.Part` is a sealed interface implemented by `String` and `StringTemplate.ValueRef` (or whatever equivalent). In this case, you could just write a processor with a simple loop like this:
>
> ```
> var sb = new StringBuilder();
> st.parts().forEach(part -> {
>   switch (part) {
>     case String -> sb.append(part);
>     case StringTemplate.ValueRef -> sb.append(formatValue(valueRef.value()));
>   }
> })
> ```
>
> A processor logic would be just much more easier to read than the double iterator counterpart (and in my opinion even easier than trying to use the stencil). An added benefit is that there would be little need to ban a character from ST in this case. Of course, the flip side is that we would need all values to be wrapped, but that doesn't seem like a high cost to me (especially if `ValueRef` would eventually be a value type, then I'm guessing this extra cost would be possible to be mostly optimized away), because it is unlikely to have so many values in an ST for this to matter. Not to mention that having double iterators would have additional cost as well.
>
> Attila

I like where you're going, but I think it can be done in a more
straightforward and simple way by moving the process() method to
StringTemplate and having it take a pair of Consumers:

public interface StringTemplate {
    default void process(Consumer<String> fragmentConsumer,
Consumer<Object>[1] valueConsumer) {
        // iterate through both lists, alternately calling
fragmentConsumer and valueConsumer
    }
}

[1] or Consumer<? super T>, see my previous posts about generic string
templates.

Using that method would look like:

var sb = new StringBuilder();
st.process(
    sb::append,
    value -> sb.append(formatValue(value))
);

Cheers,
Clement Cherlin


More information about the amber-spec-observers mailing list