Feedback: String Templates (JEP 430)

Reinier Zwitserloot reinier at projectlombok.org
Fri Mar 31 17:11:34 UTC 2023


On 31 Mar 2023 at 17:37:00, Brian Goetz <brian.goetz at oracle.com> wrote:

> The examples here are intended to motivate why the design is as it is, and
> illustrate the sort of use cases we have in mind.
>

For the JSON example, it’s just an oversimplification in the example code,
but, some sort of callout that the example code is oversimplified so much
it’s now insecure is warranted, no? Or just remove the implementation code,
given that it isn’t all that illustrative - the example works just fine
without it, given that the call-site code is the key part of the example.
Just a minor nit.


> just call `STR.process(st)` instead of `st.interpolate()`
>
>
> Your point here is, I think, that the `interpolate` is an "attractive
> nuisance", and will make it too easy for people to do the wrong thing
> without thinking.  A valid concern, but on the other hand, is the extra
> step going to deter those folks?
>

I think it’ll deter a significant amount. The obvious place to look for
‘how do I String Template Processor’ is, at least in my experience, the
same place folks look for just about any other API: The methods directly
available on StringTemplate. The easy action to take, once someone has
decided to write a processor, is to let the IDE generate the required
signature, and then auto-complete the available parameters and methods from
there. By removing interpolate() from that lowest-effort flow, you
*force* someone
to go out and look for it, which gives them some time to think and stands
at least some chance of highlighting the security implications of
st.interpolate()/STR.process(st).

This is always a very tough discussion: How much should the language design
prevent silly mistakes. On one hand, perfection is quite impossible: The
universe is far too good at coming up with incompetence, there is no way to
stop all foreseeable abuse of an API, and therefore, that cannot be a goal.
However, making an API where the obvious way to use it is subtly but
dangerously incorrect, is, and surely this requires no debate, bad API
design. The trick is figuring out where to draw the line. Especially
considering that interpolate() can always be added later, but if added,
cannot be removed, I recommend *not* including it in the first release of
this API. The javadoc of Processor and/or StringTemplate itself should
explain how one can interpolate (by using STR.process(st)), with all due
warnings about the dangers of doing so.



>
> ... but while we're here, the entire of method seems pointless. After all,
> Processor is, itself, a functional interface, so we can just delete the
> `StringTemplate.Processor.of()` part of the above snippet and it would
> exactly the same way!
>
>
> This whole section seems an unnecessary detour, and kind of a big
> distraction.
>

I felt it necessary to explain that background in order to get to the
recommendation, which is to remove the <E extends Throwable> and the static
of() method from ST.Processor. Whilst of can always be added later, the
choice to include the throwable or not looks like one that java will have
to live with once released, whichever choice is made.

Adding <E extends Throwable> here would complicate any attempt to ‘fix’ the
checked exceptions+lambdas issue java has in some later java release.
Possibly that ship has sailed (enough APIs have added it to their
functional interfaces that, if java ever adds something to help with
checked-ex-in-lambda, that feature will have to contend with it).

The greatest (in my opinion) feat of java 1.5’s generics is that the
existing java.util. APIs all survived 100% unscathed: Somebody designing
the API of j.u.ArrayList immediately after the release of generics would
come up with the exact same API that ArrayList has today. That’s great,
given that AL was designed well before 1.5. Generics thus weren’t just
“entirely backwards compatible” in the sense that the term ‘backwards
compatible’ is commonly used, but it was even more backwards compatible
than that: It was *culturally* backwards compatible.

In that sense, records were very slightly non-culture-compatible:
LocalDate.getYear() now stands out as somewhat weird: LocalDate feels like
a record (and may one day be a record, certainly it’ll get a deconstructor
in the same release of java that introduces that concept formally) - and
‘year’ feels like a property. Nevertheless, unlike records would suggest,
the method to retrieve this property is called getYear() and not year().
This is a very minor nit - there were vastly more important factors to
decide between getX() and x() style getters for records than this.

In that sense, it is odd that string processors have
checked-exception-as-a-type-var when j.u.Function interfaces don’t.
Removing it from Processor is the most obvious way to tackle that
incongruence.

Including a "Processor Writer's Guide" here would be out of scope, and
> interfere with the primary role of the JEP.
>

Yes, a full tutorial on how to use the feature is well beyond scope.
However, explaining what the JEP does to make something possible that it
claims is possible - that should be in scope, and what I’m interested in.
Even a simple throwaway line such as:

The List<String> returned by fragments() is, per lexical node in the AST,
constant, as in, same ref every time. The list of values obviously wouldn’t
be.

is all that is needed (if indeed that is how string templates are going to
work).


> But, I'm having a hard time distilling your actual concern here.  Is it
> that you think the language feature is flawed in that it cannot express
> what you think is the right way to do it, or are you simply worried that
> people will take the examples as blessing of "here's a good way to do
> something in XYZ domain", and blindly follow it?
>

The proposal as written would be better if it uses examples that don’t
require a bevy of caveats, but whether its worthwhile to spend the time on
coming up with better ones - that’s not for me to say. Probably not - that
role is better served by separate feedback. As was written at the top,
pretty much all of the feedback is more or less at ’nitpick’ level.

String templates are great for the JSON use case, that much is clear. I’m
pretty sure string templates will be a boon in some form to SQL-in-java,
but it’s less clear precisely how much of a boon it will be. It's a common
enough usecase, and one rife with the problems this proposal tries to
tackle (keeping the intermixing of java expressions and the SQL readable
without introducing SQL injection attacks), that ensuring that the proposal
as written will be suitable for that particular job is important. If e.g.
Lukas Eder (leads the JOOQ project) starts the work on figuring out how
JOOQ could be improved with string templates, and reports back - that seems
like extremely useful feedback. Trying to figure out how one would go about
it in his stead, I got stuck due to the lack of details on how the caching
feature works.


> ## Compile time checking
>
>
> Definitely a different topic :)
>
>
The idea that I can type REGEX.”(foo)+bar” and get compile-time validation
of the regex, and perhaps even compile-time construction of a thompson-NFA
style tree so that the regex loads and runs faster at runtime (shift some
of the ‘work’ to compile-time) is *very* exciting, though 😉 - I don’t see
anything fundamental in the proposed design of string templates that would
complicate work on compile-time constants / processing, so perhaps I
shouldn’t have raised the point in the first place.

— Reinier Zwitserloot
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20230331/8c603f5d/attachment.htm>


More information about the amber-dev mailing list