This seems a very strange argument to me.

Templates are by their nature dynamic -- a template has an unknown number of holes, and the holes are filled with arbitrary expressions.   People like templates because they're easy to use, and they're easy to use because they're flexible.  Consider String::format:

    String format(String formatString, Object... values)

There are many dynamic conditions that are not statically checked here; that the format string is well-formed, that the number of holes matches the number of values provided, that the types of the values are suitable for filling the holes, etc.  Every templating policy will carry their own private interpretation of these requirements, which would require much more complex type systems to capture.

When the templating policy is a well-known constant, such as java.lang.String.FMT, IDEs will be able to provide better checking based on the specification of the formatter, but that's a bonus.

You're saying here that what we should reify is not format+values, but format+types.  This is not an unreasonable choice (but, doesn't rise to the bar you've set by "the current design is bad"), but I think your argument is an implementation preference dressed up in theoretical garb.  You want the abstraction to serve the implementation (a bootstrap), so you want to shape it like what a bootstrap wants to consume.

The reality is that the current implementation can extract this information perfectly well, and can easily and cheaply test for the invariants that are needed to guard the computation.  The design choice here is that the abstraction we are exposing is one that is more useful **to the users**; the format string and associated values can now travel together as they pass through the layers of, say, a logging framework.

So we've deliberately chosen an API that is best for users, and makes a little extra work for implementors, rather than the other way around.  (And yes, the decision was informed by roads previously explored by JavaScript and Groovy.)

The other issue with the proposed design is that there is no way to declare the 
template policy as a static method, it has to be an instance method 
implementing an interface despite the fact that both JavaScript and Scala* 
support function first and lets the user adds supplementary arguments as a 
secondary mechanism (using currying in Scala and by adding a property on the 
function itself in JavaScript).

The Template policy is a SAM interface, so any static method of the right shape can be turned into a template policy with a method reference.

I suspect what you mean by "no way", is "no way to access the super-optimized implementation strategy"?  And I'll say again the two answers I've already given to that: (a) many such formatters will not benefit from the low-level implementation strategy anyway, and (b) we should design the API to serve the users, not the implementors.  THere are many more users.





On 10/17/2021 4:54 PM, Remi Forax wrote:
I've recently proposed another way to implement the templated string/template 
policies but i may not have made it clear why i think the current proposal [1] 
is bad.

First, some vocabulary, a templated string is a string with some unnamed 
parameters that are filled with the result of expressions
by example, if we use ${ expr } as escape sequence to introduce an expression
the code

   var a = 3;
   var b = 4;
   "sum ${ a } + ${ b } = ${ a + b }"

can be decomposed into

   - a string template that can be seen either as a string "sum @ + @ = @" with 
a special character (here '@') denoting a hole for each parameter
     or an array of strings ["sum ", " + ", " = ", ""] indicating the strings 
in between holes.
   - 3 parameters, param0, param1 and param2 initialized respectively with the 
results of the expressions a, b and a + b

Before talking about the current proposal, let's take a look to the way both 
JavaScript and Scala, implement the string interpolation.

For JavaScript [2], you define a function that the template as an array and as 
many parameters you need
   function foo(templateParts, param0, param1, param2) {
     ...
   }

JavaScript uses backticks `` to delimit the templated strings and ${} as escape 
sequence
so
   var a = 3;
   var b = 4;
   foo.`sum ${ a } + ${ b } = ${ a + b }`

is equivalent to

   foo(["sum ", " + ", " = ", ""], a, b, a + b)


In Scala, this mostly works the same way, there is a class StringContext that 
correspond to a templated string and you define the function foo as a method of 
StringContext that takes the parameters (in Scala, you can add methods on an 
already existing class using (abusing of) the implicit keyword).

   implicit class FooHelper(val template: StringContext) {  // adds the 
following methods to StringContext
     def foo(param0: Any, param1: Any, param2: Any) {
       ...
     }
   }

Scala uses quotes "" to delimit the templated string and ${} as escape sequence
so
   val a = 3;
   val b = 4;
   foo."sum ${ a } + ${ b } = ${ a + b }"

is equivalent to
   new StringContext("sum ", " + ", " = ", "").foo(a, b, a + b)



In summary, for both JavaScript and Scala, the generalization of string 
interpolation is a function call which takes the templates string parts as 
first argument and the parameters of the templated string as the other 
parameters.

So in Java, you would assume that
- there is an object that represents a templated string with the holes
- there is a method that takes the templated string as first parameter and the 
parameters of the templated string

But this is not how the proposed design works.

The TemplateString does not represent a string with some holes, it represents 
the string with some holes plus the values of the holes, as if the arguments of 
the parameters were partially applied. The TemplateString acts as a closure on 
the arguments, a glorified Supplier if you prefer.

Because the arguments are already inside the TemplatedString, the 
TemplatePolicy, the function that should take the template and the parameters 
does not declare the types of the parameters.
Which means that there is no way for someone that creates a TemplatePolicy to 
declare the types of the parameters, any parameters is always valid, so there 
is no type safety.

This design is not unknown, this is the GString [4] of Groovy. While it makes 
sense for a dynamic language like Groovy to not have to declare the type of the 
parameters, it makes no sense for a language like Java which is statically 
typed to not have a way to declare the types of the parameters like Scala or 
TypeScript/JavaScript do.

The other issue with the proposed design is that there is no way to declare the 
template policy as a static method, it has to be an instance method 
implementing an interface despite the fact that both JavaScript and Scala* 
support function first and lets the user adds supplementary arguments as a 
secondary mechanism (using currying in Scala and by adding a property on the 
function itself in JavaScript).

There is a good reason to support static methods in Java, a lot of use-cases 
does not requires the template policy to have additional arguments (storing 
them in an instance is not necessary) so forcing the template policy to be 
defined as an instance method means a lot of boilerplate for no good reason.

I hope i've convinced you that the current proposal for string interpolation in 
Java is not the right one.

regards,
Rémi

* for Scala, it's a method on StringContext that acts as a function that takes 
a StringContext as first parameter.

[1] https://bugs.openjdk.java.net/browse/JDK-8273943
[3] 
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
[4] https://docs.scala-lang.org/overviews/core/string-interpolation.html
[2] https://docs.groovy-lang.org/docs/latest/html/api/groovy/lang/GString.html


Reply via email to