TemplatedString feedback and extension - logging use-case, lazy values, blocks
Adam Juraszek
juriad at gmail.com
Sat May 7 13:58:24 UTC 2022
The TemplatedString proposal is amazing; I can see using it in many places.
I have a few comments that I have not seen addressed.
Logging
=======
One place where some kind of string templating is often used is logging.
Most logging frameworks use a pair of braces {}. TemplatedStrings have the
potential to revolutionize it.
Log record usually consists of the following parts that the programmer
needs to provide:
* log level (such as error, info)
* message (such as "Oh no, the value {} is negative")
* parameters (such as -42)
* exception/throwable (only sometimes)
Let's assume that TemplatedString is performant and virtually free, then
these options would be possible. I cannot say which syntax the logging
frameworks would choose:
logger.error."Oh no, the value \{value} is negative\{ex}";
logger.error("Oh no, the value \{value} is negative", ex);
logger."Oh no, the value \{value} is negative".error(ex);
Versus the existing:
logger.error("Oh no, the value {} is negative", value, ex);
Lazy Logging
============
Some logging frameworks such as Log4j2 allow lazy evaluation for example:
logger.trace("Number is {}", () -> getRandomNumber());
I must admit that doing the same using TemplatedStrings is ugly and the 6
distinct characters introducing a lazily evaluated value are impractical:
logger.trace("Number is \{() -> getRandomNumber()}");
Can we offer something better using TemplatedStrings? The JEP already talks
about evaluation. For example, this is valid:
int index;
String data = STR."\{index=0}, \{index++}";
and mentions that the TemplatedString provides a list of values:
TemplatedString ts = "\{x} plus \{y} equals \{x + y}";
List<Object> values = ts.values();
I want to propose an additional syntax. The escape sequence \( would denote
a lazily computed value - probably stored as a Supplier. Because the value
may not be evaluated, it must only refer to effectively final variables
(the same rules as for lambda expressions apply).
The following should be illegal:
int index;
String data = STR."\(index=0), \(index++)";
The logging use-case could be expressed more naturally:
logger.trace("Number is \(getRandomNumber())");
I personally can see a parallel between \(...) and ()-> as both use
parentheses. This unfortunately is the exact opposite of the syntax of
closures in Groovy, where {123} is "lazy" and (123) produces the value
immediately. Different languages, different choices.
When should this lazy expression be evaluated, and who is responsible for
it? It can either be:
* when ts.values() is called;
* when the value is accessed (via ts.get(N) or .next() on the List's
Iterator);
* manually when it is consumed by the author of the policy using an
instanceof check for Supplier.
I keep this as an open question (first we need to find out if this is even
a useful feature). It also remains open what to do when a user provides a
Supplier manually:
Supplier s = () -> 123
logger.trace("Number is \{s} \(s)");
Another way to express a Supplier is via a method reference, which should
also be supported and would suggest the equivalence of the following:
logger.trace("Number is \{this::getRandomNumber}");
logger.trace("Number is \(this.getRandomNumber())");
Blocks
======
What if the computation of the value requires multiple statements?
STR."The intersection of \{a} and \{b} is \{((Supplier) ()-> {var c = new
HashSet<>(a); c.retainAll(b); return c;}).get()}"
STR."The intersection of \{a} and \{b} is \{switch(1){default:var c = new
HashSet<>(a); c.retainAll(b); yield c;}}"
These are (as far as my imagination goes) the easiest ways to turn a block
into an expression.
What if we introduced a special syntax for blocks? One option would be
simply doubling the braces:
STR."The intersection of \{a} and \{b} is \{{var c = new HashSet<>(a);
c.retainAll(b); yield c;}}"
The last closing brace is probably not needed but keeping them balanced
will help dumb editors with highlighting.
This could be a more general syntax useful even outside TemplatedStrings
(especially in final field initialization):
var intersection = {var c = new HashSet<>(a); c.retainAll(b); yield c;};
Lazy Blocks
===========
We may want to combine the two proposed features and compute the
intersection lazily when logging:
logger.trace("The intersection of \{a} and \{b} is \({var c = new
HashSet<>(a); c.retainAll(b); return c;})")
which would be equivalent to this already supported syntax provided that
the policy understands Supplier values:
logger.trace("The intersection of \{a} and \{b} is \{() -> {var c = new
HashSet<>(a); c.retainAll(b); return c;}}")
If we introduce \( as described in Lazy Logging above and say that
\(expression) is equivalent to \{() -> expression}, then \({statements})
would naturally be equivalent to \{() -> {statements}}.
Literals
========
It was already mentioned in the JEP that it is possible to create a policy
returning a JSON object. We can also think of them as a way to express
literals. Some Java types already have a way to parse a String. Some
examples are:
DURATION."PT20.345S"
Duration.from("PT20.345S")
COMPLEX."1+3i"
Complex.from(1, 3)
US_DATE."7/4/2022"
LocalDate.of(2022, 7, 4)
This is almost as powerful as what C++ offers (
https://en.cppreference.com/w/cpp/language/user_literal).
Conclusion
==========
I am very happy with the current proposal and tried to explore some
possible extensions. I proposed a syntax for lazy evaluation of values,
blocks as values, and a combination thereof. I would like to hear from you,
whether this is something worth considering, and whether there is demand
for it.
Regards
Adam
More information about the jdk-dev
mailing list