Inline parameters for constructors

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.

3 Likes