I want to apply scala's value classes to one of my projects because they enable me to enrich certain primitive types without great overhead (I hope) and stay type-safe.
object Position {
implicit class Pos( val i: Int ) extends AnyVal with Ordered[Pos] {
def +( p: Pos ): Pos = i + p.i
def -( p: Pos ): Pos = if ( i - p.i < 0 ) 0 else i - p.i
def compare( p: Pos ): Int = i - p.i
}
}
My question: Will the inheritance of Ordered
force the allocation of Pos
objects whenever I use them (thereby introduce great overhead) or not? If so: Is there a way to circumvent this?
An implicit class is a class marked with the implicit keyword. This keyword makes the class's primary constructor available for implicit conversions when the class is in scope. Implicit classes were proposed in SIP-13.
At a fundamental level: value classes define objects which, once created, never change their value. A variable of a value type may only be changed by re-assigning to that variable. When we wish to only modify some portion of a value class (one attribute, say), we are compelled to reassign the whole object.
A value class declaration introduces a class whose instances are value objects. An identity object is a class instance or array that does have identity—the traditional behavior of objects in Java. An identity object can mutate its non- final fields and is associated with a synchronization monitor.
Value classes are a new mechanism which help to avoid allocating run time objects. AnyVal define value classes. Value classes are predefined, they coincide to the primitive kind of Java-like languages. There are nine predefined value types : Double, Float, Long, Int, Short, Byte, Char, Unit, and Boolean.
Everytime Pos
will be treated as an Ordered[Pos]
, allocation will happen.
There are several cases when allocation has to happen, see http://docs.scala-lang.org/overviews/core/value-classes.html#when_allocation_is_necessary.
So when doing something as simple as calling <
, you will get allocations:
val x = Pos( 1 )
val y = Pos( 2 )
x < y // x & y promoted to an actual instance (allocation)
The relevant rules are (quoted from the above article):
Whenever a value class is treated as another type, including a universal trait, an instance of the actual value class must be instantiated and: Another instance of this rule is when a value class is used as a type argument.
Disassembling the above code snippet confirms this:
0: aload_0
1: iconst_1
2: invokevirtual #21 // Method Pos:(I)I
5: istore_1
6: aload_0
7: iconst_2
8: invokevirtual #21 // Method Pos:(I)I
11: istore_2
12: new #23 // class test/Position$Pos
15: dup
16: iload_1
17: invokespecial #26 // Method test/Position$Pos."<init>":(I)V
20: new #23 // class test/Position$Pos
23: dup
24: iload_2
25: invokespecial #26 // Method test/Position$Pos."<init>":(I)V
28: invokeinterface #32, 2 // InterfaceMethod scala/math/Ordered.$less:(Ljava/lang/Object;)Z
As can be seen we do have two instances of the "new" opcode for class Position$Pos
UPDATE: to avoid the allocation in simples cases like this, you can manually override each method (even if they only forward to the originlal implementation):
override def < (that: Pos): Boolean = super.<(that)
override def > (that: Pos): Boolean = super.>(that)
override def <= (that: Pos): Boolean = super.<=(that)
override def >= (that: Pos): Boolean = super.>=(that)
This will remove the allocation when doing x < y
by example.
However, this still leaves the cases when Pos
is treated as an Ordered[Pos]
(as when passed to a method taking a Ordered[Pos]
or an Ordered[T]
with T being a type parameter). In this particular case, you will still get an allocation and there no way around that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With