Relate to Inline parameters for constructors.
Introduction
The Dotty inline
feature offers many new possibilities without “black magic”, especially for compile-time manipulations/value retrieval. However, this feature is disallowed for constructor parameters.
**Disclaimer: This is not (yet) a SIP. It is a “simple” proposal I’m posting on Scala Contributors to get feedback and to discuss about it.
Motivation
Allowing constructors (and given ... with
) to have inline parameters would enable new usages at both reducing overhead and evaluating compile-time values.
Example 1: compile-time typeclasses
Say we have a typeclass method you want to evaluate at compile-time (in an inline condition or using Expr#value
in a macro), for example a refined types system
//Represent a refined type
opaque type Refined[A, B] <: A = A
//Our inline typeclass
trait Constraint[A, B]:
inline def test(value: A): Boolean
//Refine a value at compile-time
inline def assertValue[A, B](inline value: A)(using inline constraint: Constraint[A, B]): Refined[A, B] =
inline if constraint.test(value) then value
else compiletime.error("Does not satisfy the constraint")
This works for typeclass instances that doesn’t require inline params:
final class Positive
given Constraint[Int, Positive] with
override inline def test(value: Int): Boolean = value > 0
assertValue[Int, Positive](1) // 1: Refined[Int, Positive]
assertValue[Int, Positive](-1) // Compile-time error: "Does not satsify the constraint"
However, this doesn’t work when (non inline because disallowed) parameters are called:
final class Not[B]
given [A, B](using constraint: Constraint[A, B]): Constraint[A, Not[B]] with
override inline def test(value: A): Boolean = !constraint.test(value)
assertValue[Int, Not[Positive]](-1) //Compile-time error, expected a known value
because constraint.test(value)
is not fully inline. As a consequence, the final condition !constraint.test(value)
isn’t either.
Allowing inline parameters would fix this limitation:
final class Not[B]
given [A, B](using inline constraint: Constraint[A, B]): Constraint[A, Not[B]] with
override inline def test(value: A): Boolean = !constraint.test(value) //Get fully inlined (if value is inlinable too)
assertValue[Int, Not[Positive]](1) //Compile-time error: Does not satisfy the constraint
assertValue[Int, Not[Positive]](-1) //-1: Refined[Int, Not[Positive]]
Example 2: reduce monadic code overhead
With this change, we could eliminate overhead of some monad transformers:
//Anloguous to `A => B` but inline. This is already doable with the current Scala version
trait InlineFunction[A, B]:
inline def apply(a: A): B
class Monad[T](inline val value: => T):
inline def flatMap[A](inline f: InlineFunction[A, Monad[B]]): Monad[A] = f(value)
inline def map[A](inline f: InlineFunction[A, B]): Monad[A] = Monad(f(value))
then
Monad(5)
.map(_ * 2)
.map(_ / 10)
.value
is inlined to 5*2/10
which is also inlined to 1
(See Inline parameters)
Design
Here are some rules:
- Like method’s
inline
parameters, constructor’sinline
parameters are immutable. - Constructors cannot be
inline
. The goal of this proposal is to allow classes/constructors to carryinline
parameters. Aninline
constructor is non sense (atleast to me). given ... with
syntax is also concerned by this change.
Here is a question asked in the old thread:
This should behave the same as classic inline
instance methods, which already exist in the current version of Scala: since dummy
is not inline
, it doesn’t desugar to Dummy(value)
so dummy("Hello World").value
will not be inlined.
For instance in current versions:
class Dummy:
inline def value: String = "hey"
def dummy: Dummy = Dummy()
dummy.value // -> dummy.value
class Dummy:
inline def value: String = "hey"
def dummy: Dummy = Dummy()
println(compiletime.codeOf(dummy.value)) // -> "hey"
Compatibility
This change shouldn’t break compatibility for existing features. However, note you cannot use a compiled/TASTY code that uses inline
parameters in a previous version which doesn’t support them simply because it is not a “simple syntactic sugar”.