To specifically indicate your second paragraph, the type that you carry with the value class can be a reference type, and you still avoid the extra distribution of objects that would normally be involved in a wrapper. For example, if you have these implicit classes:
implicit class MyInt(val underlying: Int) extends AnyVal { def inc: Int = underlying + 1 } implicit class MyString(val underlying: String) extends AnyVal { def firstChar: Char = underlying.charAt(0) } implicit class MyNonValueClassString(val underlying: String) { def firstCharNonValueClass: Char = underlying.charAt(0) }
And this code that uses them:
println(42.inc) println("hello".firstChar) println("hello".firstCharNonValueClass)
You can compile with -Xprint:flatten to see the canceled version (reformatted here for clarity):
scala.this.Predef.println( scala.Int.box(Demo$MyInt.this.inc$extension(Demo.this.MyInt(42))) ); scala.this.Predef.println( scala.Char.box( Demo$MyString.this.firstChar$extension(Demo.this.MyString("hello")) ) ); scala.this.Predef.println( scala.Char.box( Demo.this.MyNonValueClassString("hello").firstCharNonValueClass() ) );
As you can see, calling firstChar does not include the new object.
source share