Learn myself some Scala 3, episode 1: metaprogramming – inline
Martin Odersky’s keynote talk at Scala Days 2019 made me realize, that I should pay a lot more attention to Scala 3 (a.k.a. Dotty) than I did so far. For me it now seems like Scala 3 will be a huge improvement over Scala 2, in particular because it significantly increases clearness and feature orthogonality. Just take a look at how the somewhat magic – and therefore hard to understand – realm of implicits has transitioned to given
clauses which state the dependency on some “contextual” value and delegate
s which allow us to define these values – just beautiful!
I have been travelling the Scala universe for long enough to take the timeline Martin presented – Scala 3 being released in 2020 – with a grain of salt, but nevertheless I think it is time to start learning myself some Scala 3 right away. And what could be better for learning something than writing about it? Hence I decided to write this blog post and hopefully some follow-up ones.
For this blog post I picked metaprogramming, which is a fairly advanced feature. Yet I will keep it super simple which is possible thanks to a new feature in Scala 3.
Some years ago, when I was learning Scala 2 macros, I created Scala Logging, a convenient and fast logging library wrapping SLF4J, which has been improved and maintained by others for the past years. It is simple yet useful, because it allows developers to just call the logging methods like debug
, which get expanded to the check-enabled-idiom. So let us see, how this can be done in Scala 3.
To make it crystal clear, the following is what we want to achieve. We want to be able to just call the debug
method, without having to first check isDebugEnabled
:
1 | logger.debug(hello()) |
This should be compiled to the check-enabled-idiom, i.e. we want the hello
method to be evaluated only if isDebugEnabled
is true
:
1 | if (logger.isDebugEnabled) logger.debug(hello()) |
Scala 3 there comes with the new inline
keyword which is sort of an enabler for various metaprogramming features like macros. But even on its own it is quite powerful as we will see.
An inline
method is guaranteed to be inlined by the Scala 3 compiler, which means that the method body will be expanded into any call site, i.e. any code which calls the respecive method. Hence we can define a Logger
class which wraps an underlying Logger
from Log4j 2 and has an inlined debug
method which takes the log message as a by-name parameter and applies the check-enabled-idiom:
1 | import org.apache.logging.log4j.{ Logger => Underlying } |
Now let us add some code using the above:
1 | import org.apache.logging.log4j.LogManager |
When we run this code, we see that the hello
method only gets called, when the log level is set to DEBUG
. This becomes clear by inspecting the code which gets generated by the Scala 3 compiler for the main
method – we can use javap
for this purpose:
1 | ... |
As we can see, hello
only gets called (pos. 24), if the check whether the DEBUG
log level is enabled (pos. 11) has been true
; else “nothing” happens, i.e. Unit
is returned.
To sum it up, all we need to implement a convenient check-enabled logging library like Scala Logging in Scala 3, is the new inline
feature – no further metaprogramming facilities like macros, which are also available in Scala 3, are necessary.
The full source code – in a little more polished fashion – can be found at github.com/hseeberger/log4dotty.