kpbochenek
kpbochenek

Reputation: 469

scala value class multiple inheritance

I have in my project objects that represent IDs.

Let's say it is ChairId, TableId, LampId. I want them all to inherit from GenericId. And I want to be able to call def f(x: GenericId) = x.id

I want them to hold only single id: String so I would like to make them extend AnyVal.

Also I would like for each type to provide function generate which would generate my specific ID i.e. I would like to type something like ChairId.generate()

I have typed this:

sealed abstract class GenericId(val id: String)
final case class ChairId(override val id: String) extends GenericId(id)
final case class TableId(override val id: String) extends GenericId(id

And I though if GenericId would inherit from AnyVal that would work but so far no luck ;/ I also tried making GenericId a trait and make case classes extend AnyVal with GenericId but also won't compile :/

Another thing with TableId.generate() I can provide companion object just with function generate and that basically solve my problem but I wondered if there is possibility to solve that without defining companion object? (i.e. through implicits somehow)

// edit

regarding comment to provide code which doesn't compile(and I would like to):

sealed abstract class AbstractId(val id: String) extends AnyVal
final case class CatId(override val id: String) extends AbstractId(id)
final case class DogId(override val id: String) extends AbstractId(id)

Upvotes: 1

Views: 857

Answers (2)

pedrofurla
pedrofurla

Reputation: 12783

Some quotes from the value classes SIP that are likely to clarify your doubts:

Value classes...

  1. ...must have only a primary constructor with exactly one public, val parameter whose type is not a value class.

  2. ... cannot be extended by another class.

As per 1. it can not be abstract; per 2. your encoding doesn't work.

There is another caveat:

A value class can only extend universal traits and cannot be extended itself. A universal trait is a trait that extends Any, only has defs as members, and does no initialization. Universal traits allow basic inheritance of methods for value classes, but they incur the overhead of allocation.

With all that in mind, based on your last snippet, this might work:

sealed trait AbstractId extends Any { def id: String }
final case class CatId(id: String) extends AnyVal with AbstractId
final case class DogId(id: String) extends AnyVal with AbstractId

But keep in mind the allocation only occurs if you want to use CatId and DogId as an AbstractId. For better understanding I recommend reading the SIP.

Upvotes: 1

Michael Zajac
Michael Zajac

Reputation: 55569

Value classes cannot work this way for a couple of reasons.

First, from the documentation, value classes cannot be extended by any other class, so AbstractId cannot extend AnyVal. (Limitation #7)

scala> abstract class AbstractId(val id: String) extends AnyVal
<console>:10: error: `abstract' modifier cannot be used with value classes
       abstract class AbstractId(val id: String) extends AnyVal
                      ^

Second, even if you make AbstractId a trait, and define the other ids like this:

final case class DogId(val id: String) extends AnyVal with AbstractId

.. the usage of the value class wouldn't fit your case, because the class itself would still get allocated. See the allocation summary:

A value class is actually instantiated when:

  1. a value class is treated as another type.
  2. a value class is assigned to an array.
  3. doing runtime type tests, such as pattern matching.

Upvotes: 3

Related Questions