Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are value classes restricted to AnyVal?

As far as I understand value classes in Scala are just there to wrap primitive types like Int or Boolean into another type without introducing additional memory usage. So they are basically used as a lightweight alternative to ordinary classes.

That reminds me of Haskell's newtype notation which is also used to wrap existing types in new ones, thus introducing a new interface to some data without consuming additional space (to see the similarity of both languages consider for instance the restriction to one "constructor" with one field both in Haskell and in Scala).

What I am wondering is why the concept of introducing new types that get inlined by the compiler is not generalized to Haskell's approach of having zero-overhead type wrappers for any kind of type. Why did the Scala guys stick to primitive types (aka AnyVal) here?

Or is there already a way in Scala to also define such wrappers for Scala.AnyRef types?

like image 512
Bastian Avatar asked Dec 09 '22 19:12

Bastian


2 Answers

They're not limited to AnyVal.

implicit class RichOptionPair[A,B](val o: Option[(A,B)]) extends AnyVal {
  def ofold[C](f: (A,B) => C) = o map { case (a,b) => f(a,b) }
}

scala> Some("fish",5).ofold(_ * _)
res0: Option[String] = Some(fishfishfishfishfish)

There are various limitations on value classes that make them act like lightweight wrappers, but only being able to wrap primitives is not one of them.

like image 124
Rex Kerr Avatar answered Dec 22 '22 17:12

Rex Kerr


The reasoning is documented as Scala Improvement Process (SIP)-15. As Alexey Romanov pointed out in his comment, the idea was to look for an expression using existing keywords that would allow the compiler to determine this situation.

In order for the compiler to perform the inlining, several constraints apply, such as the wrapping class being "ephemeral" (no field or object members, constructor body etc.). Your suggestion of automatically generating inlining classes has at least two problems:

  1. The compiler would need to go through the whole list of constraints for each class. And because the status as value class is implicit, it may flip by adding members to the class at a later point, breaking binary compatibility
  2. More constraints are added by the compiler, e.g. the value class becomes final prohibiting inheritance. So you would have to add these constraints to any class who want to be inlineable that way, and then you gain nothing but extra verbosity.

One could think of other hypothetical constructs, e.g. val class Meter(underlying: Double) { ... }, but the advantage of extends AnyVal IMO is that no syntactic extensions are needed. Also all primitive types are extending AnyVal, so there is a nice analogy (no reference, no inheritance, effective representation etc.)

like image 44
0__ Avatar answered Dec 22 '22 17:12

0__