I didn’t receive any reply so I will assume I’m right and you asked about function re-composition.
Firslty, let’s make a summary.
Introduction
The new inline
keyword actually offers many possibilities:
- Compile-time optimizations
- Pre-computed return types (transparent inline)
- Better way to retrieve value at compile-time using a macro (FromExpr)
Actually this only applies on methods and vals. Here I propose to add the possibility to inline constructor parameters.
Motivation
We can’t actually fully inline values passed through a class. While it’s generally not necessary to fully inline a value through a class, it is required in some cases.
Let’s take the @ysthakur’s use case:
This is possible using inline parameters:
import scala.quoted.*
class Range(inline from: Int, inline to: Int) {
inline def map[T](function: InlinedFunction[Int, T]): Seq[T] = {
var i = from
val buffer: mutable.Buffer[T] = mutable.Buffer()
while(i < to) {
buffer += function(i)
i += 1
}
buffer
}
}
Then, val seq = Range(1, 100).inlinedMap(_ * 2)
will desugar into
val seq = {
var i = 0
val buffer: mutable.Buffer[T] = mutable.Buffer()
while(i < 100) {
buffer += i*2
i += 1
}
buffer
}
There is no extra Range
created at runtime. Here the class is used as a receptacle/an inlined step in the final collection building.
@Swoorup pointed another use case for this feature:
Function-composition can be handled by inline methods.
class Monad[T](val value: => T) {
def flatMap[A](f: (=> T) => Monad[A]): Monad[A] = f(value)
def map[A](f: (=> T) => A): Monad[A] = Monad(f(value))
}
With this implementation, a new Monad will be created at each call sequentially this can lead to performances issues, for example if we use monadic collections. With inline parameters, we’re able to precompute the entire chain into one, composed, function.
class Monad[T](inline val value: => T) {
inline def flatMap[A](inline f: (=> T) => Monad[A]): Monad[A] = f(value)
inline def map[A](inline f: (=> T) => A): Monad[A] = Monad(f(value))
}
The following example:
Monad(5)
.map(_ * 2)
.map(_ / 10)
.value
will desugar into 5*2/10
Design
Requirements
Like method parameters and vals, inline constructor parameters have to be immutable.
The constructor doesn’t have to be inline itself (what would be an inline constructor ?)
General behaviour
Inline parameters will behave exactly like inline vals:
case class Dummy(inline value: String)
Dummy("Hello World").value //Inlined to "Hello World"
Rules for overriding
If an inline parameter overrides another, non-inline parameter, the inline parameter can also be invoked at runtime.
Due to the inline nature, inline parameters are effectively final.