RFC: embedded string interpolations
This issue is a follow-up from https://github.com/http4s/http4s/issues/3433.
I've struggled with this a bit and think it's quite problematic to embed string interpolations to uri"" and path"" of http4s (actually, it's so for every interpolated macros based on literally):
- At the moment, compilation fails if invalid strings have been passed (underlying
validateis calling at compile time). - Say
val foo = "/foo/bar"
val someuri = uri"https://example.com$foo"
means that we should check the value of foo at the runtime (generally, foo could be some computation, not a constant value). So bringing embedded string interpolations means we should proceed with runtime values at compile time (because of 1). I'm not that much a Scala macros astronaut, but am feeling it's probably impossible.
UPD: speaking more accurately, compile-time checking is only possible for the literal part of the passed string.
If anyone has insights about that, please share them here.
Yes, that's correct. It's possible to support interpolation of constant values at compile time, but not possible to use non-constant values. Doing so would require changing the macro to return an Either[String, Uri] when there are one or more non-constant parts and Uri otherwise. This type switching would require a white box macro in Scala 2 and transparent inline in Scala 3.
To support interpolation of constant values, we could do something like this:
trait Literally[A]:
type Quotes = scala.quoted.Quotes
type Expr[A] = scala.quoted.Expr[A]
val Expr = scala.quoted.Expr
def validate(s: String)(using Quotes): Either[String, Expr[A]]
def apply(strCtxExpr: Expr[StringContext], argsExpr: Expr[Seq[String]])(using Quotes): Expr[A] =
apply(strCtxExpr.valueOrAbort.parts, argsExpr.valueOrAbort)
private def apply(parts: Seq[String], args: Seq[String])(using Quotes): Expr[A] =
val str = parts.zipAll(args, "", "").map(t => t(0) + t(1)).mkString("", "", "")
validate(str) match
case Left(err) =>
quotes.reflect.report.error(err)
???
case Right(a) =>
a
Usage:
scala> inline val x = "4"
scala> val y = port"1${x}2"
val y: org.typelevel.literally.examples.Port = Port(142)
This is too limited though as it requires the args list to be strings. It would be nicer to support arbitrary types there. Overall, I don't think the complexity is worth it.