Custom String Templates

Scala has a great feature that allows for adding custom string interpolation handlers, signaled by adding a prefix in front of the opening quote. They have built-in versions s"" and f"" (the latter which allows adding %.2f style formatting strings after the ${}).

But they also allow custom prefixes, which my company has used to very good effect for building a powerful i18n framework and SQL layer. There are a number of ways to expose it. Here is one suggestion:

Allow adding a function in a specific spot, e.g.:

fun templateHandler(prefix: String, parts: Array<String>, args: Array<Any>): String

Where prefix is the thing that comes before the opening quote, parts are the string literals between interpolated expressions, and args are the interpolated expressions.

If the template starts or ends with an interpolated expression, parts should respectively start or end with an empty String so that the every-other pattern of the parts and args can be relied upon.

This would allow building things like:

  • json"a: $a" (where a can be an array and will get formatted correctly)
  • sql"select …" (and do proper escaping of arguments)
  • tr"You have a balance of $count credits remaining. " (and get translated)

Because the existing string interpolation functionality does not have a prefix concept, it should be possible to add this in a backwards-compatible way and without impacting performance for the existing approach.

It is essential that the handler function could be non-static so that it can access and mutate relevant state in an object instance (i.e., access the this pointer), and so that different handlers can be implemented in different classes.

There is a tension between requiring a class to implement an interface to enable this vs. allowing static implementations. Ideally it would be possible to do both, but allowing access to instance state is very important for the use-cases that we have encountered at my company.

And it should not be required that the handler return a String, as many use cases have side-effects but do not return anything.

(Back-story: we are frustrated with Scala compile times and are considering a switch to Kotlin; this is a must-have feature to enable the switch.)

2 Likes

Is it possible to combine templateHandlers?

I don’t understand the question. I think it should be best understood as syntactic sugar that maps:

abc"Interpolate this: ${a}"

to:

templateHandler(“abc”, arrayOf<String>("Interpolate this: ", “”), arrayOf<Any>(a))

And wherever the call is made, it will take whatever templateHandler method is in scope, whether it be a class function or some top-level function that is accessible. Basically, the semantics would be whatever would get called if the string template where literally replaced with the translated call in the code file.

Alternatively, in order to allow for different return types for each prefix, you could have a naming convention such as:

fun templateHandler_abc(parts: Array<String>, args: Array<Any>): Unit
fun templateHandler_xyz(parts: Array<String>, args: Array<Any>): String

Then the compiler can make sure that there is an available handler function to match the prefix present in the expression. That is more in line with how Scala does it (though there are still significant differences).

I suggest you to find a Kotlin way or remain on Scala.
It is really difficult to get this feature soon.

Please consider to find a solution to your problem, I don’t think that custom string template is a game changer.

Regarding your proposal, can you explain better how it works?
Example:

sql"SELECT count(*) FROM $table WHERE $attribute = $value"

json"name: $userName, friends: $friendNameList"

Our main make-or-break use case is our HTML builder with internationalization (i18n). Similar in some ways to the sketch provided at:

https://kotlinlang.org/docs/reference/type-safe-builders.html

But instead of using the + operator to append the string literals, we use custom Scala string interpolators.

This allows us to generate valid ICU4J MessageFormats, complete with type-safe translation-and-type-aware interpolation of the ${} content. Interpolated content can be fully machine-rendered (like numbers, currency, or dates), or it can be a mixture (such as plural forms, where the machine picks the plurality based on an input number, but the translators provide the inflected forms).

In this way, we are able to code HTML in-framework, using the same type-safe language that we use for everything else, all while getting i18n essentially for free (free from a coding perspective; we pay translators to translate the ICU MessageFormat phrases).

At the end of the day, we make an edit in Scala and then deploy. The ICU MessageFormats get collected in a production database and our translators are notified. They complete the translations in 24 hours and our site is back to full i18n support without any additional effort on our part compared to an English-only site. This has allowed our 2-person team to run half a dozen sites fully translated into 8 languages other than English.

This is in contrast to the crazy systems of externalizing all phrases that need to be translated and writing them in native ICU MessageFormat, which is extremely difficult to type correctly without errors. Those systems also make the page content essentially unreadable in the code because all the text content has been externalized.

Why is it difficult to get this feature soon?

Generally a feature like this has to first be introduced as a KEEP. Then the kotlin team has to decide to adopt it and it needs to be implemented. Even if the kotlin team instantly decides to implement it, it will probably not happen before the compiler backend rework is done, so at best it will be available in an experimental state about halfway between 1.4 and 1.5, probably not before 1.5.

This is the first time I hear about the feature that you are requesting. Hence I believe there is not so much popular demand for it.

There are lots of other features for which the popular demand is very high, and which are already waiting to be implemented for years. Literally years.

Even if the feature request is accepted, I would keep expectation low that it will ship sooner than 2 years from.