Description
This issue is to allow inline
code to customise its output based on user-defined settings provided as scalac flags.
Background
Full detail here: https://contributors.scala-lang.org/t/metaprogramming-configurability/4961
This issue is the first part of the solution I proposed above.
Proposal
Disclaimer: the names used below are just drafts and still need some good bikeshedding.
- Add a new scalac flag that takes a
-E:key=value
(similar to how java accepts-Dkey=value
args) - Add
transparent inline def envGet(inline key: String): Option[String]
toscala.compiletime
that provides access to the settings specified with above scalac flags
Usage Example
The very first example in the inlining doc is a logger that can be configured at compile-time, and is "zero-cost" (as they say) at runtime.
- when
logging = true
then calls tolog()
generateprintln
statements - when
logging = false
then calls tolog()
generate nothing - there is never a logging-config check at runtime
Currently, the problems with this are
Logger
and itsConfig
are static, and changing the setting is a code changeConfig
must be pre-configured and defined alongsideLogger
(elseLogger
wouldn't compile)- If
Logger
were a library, downstream users would have no way of configuring it
If this issue were implemented, our zero-cost-ish Logger
could be written like this below, to accept a "myLogger.level"
setting that downstream users can populate:
import scala.compiletime.*
object Logging {
private inline val Trace = 0
private inline val Debug = 1
private inline val Info = 2
private inline val Warn = 3
private transparent inline def chosenThreshold: Int =
inline envGet("myLogger.level") match
case Some("TRACE") => Trace
case Some("DEBUG") => Debug
case Some("INFO") => Info
case Some("WARN") => Warn
case None => Warn // let's provide a default out-of-the-box
case Some(x) => error("Unsupported logging level: " + x)
private inline def log(inline lvl: Int, inline msg: String): Unit =
inline if lvl >= chosenThreshold then println(msg) else ()
// This is the public API
inline def trace(inline msg: String) = log(Trace, msg)
inline def debug(inline msg: String) = log(Debug, msg)
inline def info (inline msg: String) = log(Info , msg)
inline def warn (inline msg: String) = log(Warn , msg)
}
And then a downstream user could specify -E:myLogger.level=INFO
in their scalac flags so that our toy logging library effectively becomes this for them:
object Logging {
inline def trace(inline msg: String) = ()
inline def debug(inline msg: String) = ()
inline def info (inline msg: String) = println(msg)
inline def warn (inline msg: String) = println(msg)
}
Reconsidering the three problems above, with this new solution:
Logger
can now be configured dynamically, and via config (no code changes required)Config
is no longer necessary. Whether the library author wants to provide it and/or defaults is now something under their control.- Downstream users can now configure the behaviour of
Logger
PR?
I'm not asking that the busy Scala 3 team take some time out to implement this. This seemed simple enough for me to try implementing it myself and so I have, and it seems to work well! The above logging example is one of the tests and is confirmed to work as excepted.