Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In these cases, the Scala value class will be "boxed", right?

If I have this value class:

class ActionId(val value: Int) extends AnyVal

Then, in all the examples below, an object will be allocated for the value class? (It'll be "boxed" — it will not simply be unwrapped to a plain 32 bit integer, right?)

  1. A function that returns a value class — the value class escapes the scope and will hence be "boxed"?

    def someFunction(): ActionId = {
      ...
      return ActionId(123)
    }
    
  2. A function that returns an object with a value class member — the value class escapes the scope and will hence be "boxed"?

    case class Post(id: ActionId, ...) { ... }
    
    def someFunction(): Post = {
      ...
      val somePost = Post(ActionId(123), ...) // ActionId will be "boxed", right?
      return somePost
    }
    
  3. Even if the object with a value class member is not returned (doesn't really escape the scope), the value class will still be "boxed", when it is used as a member of another class (as a field in the Post class, in this example)?

    def anotherFunction() {
      ...
      val somePost = Post(ActionId(123), ...) // "Boxed" here too, right?
    
      // ... do something with somePost
    
      // But don't:  return somePost
    
      // However some *other* similar functions *do* return `somePost` — so
      // class `Post` must be able to box the ActionId? Hence it's boxed (above)?
    }
    

Related to this is this answer, which says that when the value class doesn't escape the scope, it's effectively being inlined. It refers to Scala Improvement Process document SIP-15 for more details. However as far as I can tell SIP-15 actually doesn't mention that a value class instance that escapes the scope will be "boxed". But I think it seems reasonable that it would have to be "boxed". (Why doesn't the SIP explicitly state that it will be boxed if it escapes?)

like image 658
KajMagnus Avatar asked Apr 07 '13 07:04

KajMagnus


3 Answers

None of your examples result in boxing. Value classes are only boxed with generics, with arrays, and when typed as a superclass/trait (e.g. Any/AnyVal)

They're boxed with generics because otherwise you can't distinguish them from the value (and primitives need a box anyway). Same deal with Any, and other superclasses/traits need a box or the type relationship is wrong.

They're boxed with arrays because arrays need to know the type of the contents, but the JVM doesn't understand the concept of a "value type". So you'd end up with an array that said it was the type being boxed, but which Scala was pretending was an array of the value type; a decision was made (based on previous issues with Array when it wasn't just a plain Java/JVM array) that this would lead to too many subtle bugs and corner cases.

Here's an example of the three ways to get boxing:

trait Q extends Any {}
class X(val x: String) extends AnyVal with Q {}

// Array
val a = Array(new X("salmon"))   // boxed

// Generic
val b = Option(new X("minnow"))  // boxed

// Upcast
val c = (new X("perch"): Any)    // boxed
val d = (new X("cod"): AnyVal)   // boxed
val e = (new X("herring"): Q)    // boxed

Everything else--passing around through various functions, etc.--doesn't require boxing, including all of your examples.

Arrays are a bit of a special case because you can store the primitives and pull them out again as value classes with zero bytecode overhead, but a lot of syntactic overhead:

class Y(val repr: String) extends AnyVal {}
val y1 = new Y("apple")    // unboxed
val y2 = new Y("orange")   // unboxed
val ys: Array[String] = Array(y1.repr, y2.repr)   // no overhead
val y3 = new Y(ys(0))      // no overhead
like image 77
Rex Kerr Avatar answered Nov 02 '22 11:11

Rex Kerr


In all three cases there will be no boxing at all.

It's quite easy to check by your self:

class ActionId(val value: Int) extends AnyVal
object Foo {
  def someFunction(): ActionId = {
    new ActionId(123)
  }
}

Now lets run scalac, and we will have a bunch of class files (files with bytecode). The one that we need is Foo\$.

» javap Foo\$ 
Compiled from "Boxing.scala"
public final class Foo$ extends java.lang.Object{
    public static final Foo$ MODULE$;
    public static {};
    public int someFunction();
}

As you can see, even if value class leaks from function generally it wouldn't be boxed.

case class Post(id: ActionId, notion: String)

object Foo2 {
  def someFunction(): Post = {
    Post(new ActionId(123), "So ActionID will be boxed?")
  } 
}

scalac => javap:

» javap Post  
Compiled from "Boxing.scala"
public class Post extends java.lang.Object implements scala.Product,scala.Serializable{
    public static scala.Function1 tupled();
    public static scala.Function1 curried();
    public int id();
    public java.lang.String notion();
    public Post copy(int, java.lang.String);
    public int copy$default$1();
    public java.lang.String copy$default$2();
    public java.lang.String productPrefix();
    public int productArity();
    public java.lang.Object productElement(int);
    public scala.collection.Iterator productIterator();
    public boolean canEqual(java.lang.Object);
    public int hashCode();
    public java.lang.String toString();
    public boolean equals(java.lang.Object);
    public Post(int, java.lang.String);
}

As you can see id here represented as plain int (e.g. third method).

Finally, will value class boxed if the object with a value class member is not returned (doesn't really escape the scope)?

case class Post(id: ActionId, notion: String)

object Foo3 {
  def anotherFunction(): Unit {
    val post = Post(new ActionId(123), "Will be boxed?")
  } 
}

If we look closely at bytecode of the method, here is what we will see:

Code:
   Stack=4, Locals=2, Args_size=1
   0:   new #15; //class Post
   3:   dup
   4:   bipush  123
   6:   ldc #17; //String Will be boxed?
   8:   invokespecial   #20; //Method Post."<init>":(ILjava/lang/String;)V
   11:  astore_1
   12:  return
  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      13      0    this       LFoo3$;
   12      0      1    post       LPost;

There is no boxing of int in ActionId. If it would box you would see something like this one:

Code:
   Stack=5, Locals=2, Args_size=1
   0:   new #15; //class Post
   3:   dup
   4:   new #17; //class ActionId
   7:   dup
   8:   bipush  123
   10:  invokespecial   #20; //Method ActionId."<init>":(I)V
   13:  ldc #22; //String Will be boxed?
   15:  invokespecial   #25; //Method Post."<init>":(LActionId;Ljava/lang/String;)V
   18:  astore_1
   19:  return
  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      20      0    this       LFoo3$;
   19      0      1    post       LPost;

You see, the difference is bipush 123 versus

   4:   new #17; //class ActionId
   7:   dup
   8:   bipush  123
   10:  invokespecial   #20; //Method ActionId."<init>":(I)V
like image 23
om-nom-nom Avatar answered Nov 02 '22 12:11

om-nom-nom


With some implicit casts it is actually possible to get around the array-issue without the syntactic required by rex-kerr. I used it in conjunction with How to reduce the number of objects created in Scala?

Y.scala:

import scala.language.implicitConversions

class Y(val repr: String) extends AnyVal {}
object Y {
    implicit def stringToY (v:String) = new Y(v)
    implicit def yToString (v:Y) = v.repr
}

Main file:

import Y._

val y1 = new Y("apple")    // unboxed
val y2 = new Y("orange")   // unboxed
val ys: Array[String] = Array(y1, y2)   // Implicit cast
val y3:Y = ys(0)
like image 3
Morty Avatar answered Nov 02 '22 11:11

Morty