Templates are an *implementation* mechanism; they're not an API building tool.  If you want stronger type checking, use the existing API construction features of the language -- such as exposing a method that takes a list of arguments of specific types, or takes a record; if the template contains lots of repetition, use a builder, etc, and then let the method/builder do the templating.

I agree it would be nice to be able to preflight the invocation at compile time, so that validity errors could be caught at compile time and turned into diagnostics, but this is broader than just type checking -- this includes malformed template strings, etc.  That's a desirable feature that I hope we'll be able to layer on later. (Again, this is somewhat related to the compile-time constant folding feature, since that involved the compiler reflectively calling library code at compile time to at least partially validate compile-time constant information.)



On 10/18/2021 3:12 PM, fo...@univ-mlv.fr wrote:
----- Original Message -----
From: "Brian Goetz" <brian.go...@oracle.com>
To: "Remi Forax" <fo...@univ-mlv.fr>, "amber-spec-experts" 
<amber-spec-experts@openjdk.java.net>
Sent: Lundi 18 Octobre 2021 18:47:00
Subject: Re: Templated String and template policies, why the current design is 
bad
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.
There is a lot of structured text that ask for specific types in a specific 
order.

By example, if a text that starts with a date and then some values

   new DatedTextTemplatePolicy()."""
     // Date \(LocalDate.now())
     \(key1) : \(value1)
     \(key2) : \(value2)
   """;

If i can declare the parameters like in JavaScript, i can write
   String apply(TemplatedString template, LocalDate date, Object... pairs) { 
... }


It also make all the constructs that are target typing, unusable.
By example, how to use lambdas/method references that will be used as 
projection functions for several record instances.

   List<Person> persons = ...

   // generate all mails
   new MailGeneratorTemplatePolicy(persons)."""
     Dear \(Person::title) \(Person::lastName),
     i hope you enjoy ...
     ...
   """;

As you know, you can not write this kind of code if the arguments are all typed 
Object.


Another example is there grammar example of John,
https://urldefense.com/v3/__https://github.com/forax/java-interpolation/blob/master/src/test/java/com/github/forax/interpolator/GrammarTemplatePolicyTest.java*L22__;Iw!!ACWV5N9M2RV99hQ!asJ_WOx0QOyBjnKlGynqHgivYFVsbSTL8xDyi-0kCyI_qHiDdLU_IZ4tPbvTT0ua2w$

Here you want all the arguments to be either a terminal or a non-terminal.
It should be a compile time error if a user uses something else.


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.
Nope, i want compile time safety when it's possible, Object.. should be a 
possible descriptor for the types of the parameters not the only descriptor.

[...]

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.
Yes, that why i call it a glorified Supplier, but i don't see how it helps.

In term of writing the code, in an IDE, i can not take a type Pattern." + 
CTRL-SPACE.

As a JDK maintainer, you can cheat and say, do this import static on that class 
and all template policies you need are now available, but this approach does 
not scale.

Otherwise, you have to memorize that FMT is in fact FormatTemplatePolicy.FMT, 
that PATTERN is in Fact PatternTemplatePolicy.PATTERN, etc.


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.
It's a false dichotomy, i want both, an API easy to use and efficient.

But in this thread i would like us to focus on the type checking part, the 
efficiency can be discussed in another thread.

Rémi





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://urldefense.com/v3/__https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals__;!!ACWV5N9M2RV99hQ!asJ_WOx0QOyBjnKlGynqHgivYFVsbSTL8xDyi-0kCyI_qHiDdLU_IZ4tPbsWqtWjzg$
[4] 
https://urldefense.com/v3/__https://docs.scala-lang.org/overviews/core/string-interpolation.html__;!!ACWV5N9M2RV99hQ!asJ_WOx0QOyBjnKlGynqHgivYFVsbSTL8xDyi-0kCyI_qHiDdLU_IZ4tPbtRBYzatg$
[2] 
https://urldefense.com/v3/__https://docs.groovy-lang.org/docs/latest/html/api/groovy/lang/GString.html__;!!ACWV5N9M2RV99hQ!asJ_WOx0QOyBjnKlGynqHgivYFVsbSTL8xDyi-0kCyI_qHiDdLU_IZ4tPbsju7OQhw$

Reply via email to