First they came for my s-interpolator

In Scala 2, if I don’t like the library’s s-interpolator, I can write my own by, for example, dropping a custom StringContext object in a convenient parent package. I can disable s-interpolation by not writing an s method. I can disable interpolation by not writing an accessible apply. My apply need not return a type named StringContext; it can return MyStringMacros.

If I want to boost my debug strings temporarily, I can supply my custom context just during development, just as we tweak some logging config. Later, s"Error: $e" returns to the boring standard interpolator.

In Scala 3, however, the compiler-generated reference to StringContext is rooted, as _root_.scala.StringContext, so that the desugaring can no longer be customized in this way.

This change was made casually a few years ago, perhaps during a warm spell in midsummer.

Since there was no SIP or public discussion that I know of, I invite discussion here.

Full disclosure, several years ago, I contributed this “rooted StringContext” to Scala 2 and was told no, it’s intended to work that way. One person’s lack of hygiene is another’s embrace of fragrant nature.

More disclosure, I still think it’s unfortunate we can’t write etapolators such as res.tap(log debug s"it was $_"). Using your debug logging interpolator, that is res.tap(trace"it was $_").

My bias is toward letting the user do what comes naturally.

Recently I was tweaking the unindenter and asked, per Amelia Bedelia, why is it the i and not the u interpolator, as it does not indent but unindents. (I remember from childhood that Amelia insists you ask that she undust the furniture, if that’s what you want her to do. From tv, we learn to say “dust for fingerprints”, which is the opposite of what the criminals do when they “wipe it down” to ensure they leave no trace of their guilt.) I shrugged and thought, Well, you can always call it s, because why not. In the compiler codebase, it’s called sm not for S&M but for stripMargin.


By coincidence, at the time of this change to dotty in 2018, I did some linting on Scala 2. You know linting is an easy way to help out. The related change was that the internal name for StringContext must not itself be called StringContext because that breaks interpolation wherever that member is visible.

I’d like to call out my comment on that PR:

Thanks for the review of tedious material. You make a PR better than it deserves to be!

We need a SIP for that!

Well hang on a sec, I can do statement interpolation with Blindsight:

val dayOfWeek = "Monday"
val temp = 72 
val statement: Statement = st"It is ${dayOfWeek} and the temperature is ${temp} degrees."

Are you talking about an explicit unnamed “$_”, or a “trace” interpolator specifically?

Yes, it would be nice if we could do that.

As a workaround, you can define:

opaque type It[+A] = A

def it[A](using It[A]): A = summon[A]

extension [A](x: A) def logged(msg: It[A] ?=> String): A =
  println(msg(using x))

Used as in:

val x = "hello".length.logged(s"len = $it") * 2
println("res: " + x)
len = 5
res: 10