Extension methods and type erasure

#1

Does anyone know how extension methods in Scala 3 will be limited by type erasure?
I watched a video by Martin Oderski where he gives his favorite scala 3 features. At time 29m30 he gives a very very high level example.

object StringOps {
  def (s:String) * (x:Int) :String = 
    if (x<=0) "" else s * (x-1) ++ s
}
import StringOps._

"hello" * 3

I wasn’t able to test this with Scastie. The example dies for other reasons.

However, the question is whether this is intended to work for more complicated types such as.

object StringOps {
  def (s:Array[String]) * (x:Int) :Array[String] = ...
}
import StringOps._

Array("hello","world") * 3
#2

I’ve fixed the example for you: https://scastie.scala-lang.org/rogZkmRFRKOq5NKr0aqBsQ

object StringOps {
  def (s:String) * (x:Int) :String = 
    if (x<=0) "" else s * (x-1) ++ s
}
object Main extends App {
  import StringOps._

  println("hello" * 3)
}

Prints: hellohellohello

Type erasure doesn’t affect concrete types of array elements, so for example:
Array[Int] is erased to Array[Int]
Array[String] is erased to Array[String]
Array[List[Int]] is erased to Array[List[_]]
Array[Array[Int]] is erased to Array[Array[Int]]
etc

Arrays in Java aren’t based on generics. Arrays were present before generics and type erasure. Scala’s arrays closely map to Java’s equivalents, although the syntax is different. In Java we have e.g. int[] for array of ints which clearly shows we don’t use generics. For a list of ints in Java we use List<Integer> which is the general notation for generics. In Scala both examples look very similar i.e. Array[Int] and List[Int].

#3

BTW Martin goes through some of the ramifications of this new syntax for type classes starting at time 35m49 of the video mentioned above.

#4

So List[Int] works! :slight_smile:

object StringOps {
  def (s:String) * (x:Int) :String = 
    if (x<=0) "" else s * (x-1) ++ s
  
  def (a:List[Int]) * (x:Int) :List[Int] =
  	if (x<=0) List() else a * (x-1) ++ a
}
object Main extends App {
  import StringOps._

  println(List(42,42,41) * 3)
}

This one prints out List(42,42,41,42,42,41,42,42,41) as shown here.

And we can also defined methods on parameterized types as shown here.

object StringOps {
  def (s:String) * (x:Int) :String = 
    if (x<=0) "" else s * (x-1) ++ s
  
  def (a:List[T]) *[T] (x:Int) :List[T] =
  	if (x<=0) List() else a * (x-1) ++ a
}
object Main extends App {
  import StringOps._

  println(List(42,42,41) * 3)
  println(List("hello","world")*4)
}

This prints out

List(42, 42, 41, 42, 42, 41, 42, 42, 41)
List(hello, world, hello, world, hello, world, hello, world)
#5

As you’ve found, this works, and it’s actually a good example of what type erasure is about.

Remember: erasure means that type parameters don’t exist at runtime. That means that operations that occur at runtime, such as pattern matching, can get foiled by erasure: type parameters are gone by then, so you can’t make use of them.

But type erasure is irrelevant to anything that happens at compile time. That’s why it doesn’t affect type classes (the compiler chooses which typeclass instance to use at compile time), and why it doesn’t affect extension methods (which get resolved at compile time).

Granted, you need to develop the intuition about what’s happening in the compiler vs what’s happening at runtime. But given that, the rule for erasure is actually pretty straightforward…

2 Likes
#6

I still have to think about this, I think these extension methods should make it easier to define the treeMapReduce method on various classes of types. However, it is still not clear how to avoid reimplementing it 12 times for 12 different types. It seems like we still need implicit conversions, which in Scala 3 are reorganized into given instances, if I understand correctly.

#7

Well, remember – you’re not actually implementing treeMapReduce 12 times, you’re implementing foldMap 12 times. It’s all about figuring out what’s common and what’s distinct: only the distinct bits need to be in the typeclass.

No, that’s yet a different concept – there’s a fairly lightweight first-class notion of Conversions in Scala 3. given is all about typeclasses themselves: sometimes related, but different ideas…