Inline parameters for constructors

I’m not really sure if extensions methods can be defined inline

They can

Another example could be something like this:

extension[T](monad: Monad[T]) {
  def as(value: T): Monad[T] = monad.map(_ => value)
}

I don’t really see the use case of a such method.

It would make sense that this should work, particularly if you want to provide some extra assurance that pure has “exactly once” semantics (and the cost of an allocation may well be worth it to gain that guarantee).

Ok so there is currently no issue with the above summary ? Great :smile:

I grabbed it because it actually exists in Cats, and it came to mind because the example was a Monad, and it was a method I knew there was a pre-existing usecase for.

Yep, at least as far as I can tell, it’s currently internally consistent and unsurprising as-is.

As an understanding check, is this how you understand the stepwise inlining would go for the Range example?

Class definition

This is identical to the summary above, I’m copying it here for reference

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
  }
}

Base

val seq = Range(1, 100).inlinedMap(_ * 2)

Stage 1

I’m going to handwave the compiler generating a unique identifier for i, to, from, etc

val seq = {
  val r = Range(1, 100)
  r.inlinedMap(i => i * 2)
}

Stage 2

val seq = {
  val r = Range(1, 100)
  var i = r.from
  val buffer: mutable.Buffer[T] = mutable.Buffer()
  while(i < r.to) {
    buffer += i * 2
    i += 1
  }
  buffer
}

Stage 3

This caches the inline parameters, to avoid multiple execution, which I think is consistent with the inline constructor parameters being by-value vals, rather than being by-name.

The implications of this is that, if we want to be able to have by-name semantics, the user would have to supply a wrapper and have the actual inline parameter be a function value.

val seq = {
  var i = 1
  val buffer: mutable.Buffer[T] = mutable.Buffer()
  while(i < 100) {
    buffer += i * 2
    i += 1
  }
  buffer
}
1 Like

I grabbed it because it actually exists in Cats, and it came to mind because the example was a Monad , and it was a method I knew there was a pre-existing usecase for.

Interesting. I will check that

This is identical to the summary above, I’m copying it here for reference
code…

That’s exactly what I mean.

val seq = {
  val to = r.to
  val from = r.from
  var i = from
  val buffer: mutable.Buffer[T] = mutable.Buffer()
  while(i < to) {
    buffer += i * 2
    i += 1
  }
  buffer
}

Here, r.from and r.to are inline. I think you wrote them because we don’t have any identifier/unknown for them.

I’m not sure about the correctness of my sentence so I’ll make an example:

If I do Range(a, b).map(_ * 2), r.from = a and r.to = b. I will get:

val seq = {
  val to = a //r.from is inlined to b
  val from = b //r.to is inlined to a
  var i = from
  val buffer: mutable.Buffer[T] = mutable.Buffer()
  while(i < to) {
    buffer += i * 2
    i += 1
  }
  buffer
}

This caches the inline parameters, to avoid multiple execution, which I think is consistent with the inline constructor parameters being by-value val s, rather than being by-name.

Is this cache system already implemented for inline definition or is it a proposal ?

EDIT: Rectified by the author. (See the post below)

1 Like

Yep, that’s a typo on my part, corrected.

It’s a guess, based on how I understood the semantics of the proposal. I’d have to test to see how the existing inlining works. If that’s not how it should work, or not how it works today, it’s probably worth clarifying never mind, I took another pass through the docs, and inline parameters have by-name semantics, I’ll correct my code snippet. Might be worth dropping the requirement that parameters be vals to avoid confusion.

Might be worth dropping the requirement that parameters be val s to avoid confusion.

Don’t inline variables have to be immutable ?

2 Likes

I don’t think so, at least the ones for inline methods don’t seem to need to be immutable. You might be thinking of AnyVal classes?

Both these seem to work:

inline def test0(a: => Int): Unit = { 
  while (a >= 0) {
    println(a)
  }
}

inline def test1(inline a: Int): Unit = { 
  while (a >= 0) {
    println(a)
  }
}

@main def run(): Unit = {
  var tmp0 = 5
  test0 {
    tmp0 = tmp0 - 1
    tmp0
  }
  
  var tmp1 = 5
  test1 {
    tmp1 = tmp1 - 1
    tmp1
  }
}
3
1
-1
3
1
-1

Scastie

Correct me if I’m wrong but in your example, a is immutable.

In your code, a is inlined to

tmp1 = tmp1 - 1
tmp1

but it is never reassigned: actually, a is a val.

For example, this doesn’t work:

inline def foo(inline a: Int): Unit = {
  //Parameters (inline or not) are effectively immutables. we have to create an extra var
  inline var mutable: Int = a
  while(mutable > 0) {
    println(mutable)
    mutable-=1
  }
}

@main def test = foo(5)
Modifier `inline` is not allowed for this definition

Scastie

Inline variables can’t be reassigned because they actually aren’t “variables” once compiled.

Sort of? I think it’s getting into, “from a certain point of view” territory, as my mental model of it is that a doesn’t actually exist because it gets inlined away :man_shrugging:

Part of the confusion might be that annotating a constructor parameter with var isn’t something I’ve ever seen in the wild, so I didn’t actually remember that was a thing you could do :man_facepalming:. It probably doesn’t help that my mental model doesn’t have a tight correspondence between val and “immutable”, so I was the one that was getting it mixed up with the requirement that AnyVal classes have their sole parameter explicitly annotated with val, sorry about that

It’s not a point of view but I agree it can be confusing. Your a has a value:

tmp1 = tmp1 - 1
tmp1

but this value doesn’t change. tmp1 do, not a.

Hello,
Last time talked about inline classes. By chance I found this old pending sip from @odersky about Inline Classes

The goal of this SIP draft is the same as in this discussion, leaving 2 choices:

  • Inline parameters with the behaviour described above
  • Fully inline classes
2 Likes