Множественное наследование класса значений scala

В моем проекте есть объекты, которые представляют идентификаторы.

Допустим, это ChairId, TableId, LampId. Я хочу, чтобы они все унаследовали от GenericId. И я хочу иметь возможность звонить def f(x: GenericId) = x.id

Я хочу, чтобы они держали только одного id: String поэтому я хотел бы заставить их расширить AnyVal.

Также я бы хотел для каждого типа предоставить функцию generate который будет генерировать мой конкретный идентификатор, т.е. я хотел бы напечатать что-то вроде ChairId.generate()

Я набрал это:

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

И я подумал, что если GenericId будет наследовать от AnyVal, это сработает, но пока не повезло;/ Я также попытался сделать GenericId отличительной чертой и заставить классы расширений расширять AnyVal с помощью GenericId, но также не будет компилироваться:/

Еще одна вещь с TableId.generate() Я могу предоставить объект-компаньон только с функцией generate и это в основном решает мою проблему, но я задавался вопросом, есть ли возможность решить это без определения объекта-компаньона? (т.е. как-то через импликации)

// редактировать

относительно комментария, чтобы предоставить код, который не компилируется (и я хотел бы):

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)

2 ответа

Решение

Классы значений не могут работать таким образом по нескольким причинам.

Во-первых, из документации классы значений не могут быть расширены никаким другим классом, поэтому AbstractId не может расширяться AnyVal, ( Ограничение № 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
                      ^

Во-вторых, даже если вы делаете AbstractId признак, и определите другие идентификаторы как это:

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

.. использование класса значений не подходит для вашего случая, потому что сам класс все равно будет выделен. Смотрите сводку распределения:

Класс значения фактически создается, когда:

  1. класс значения рассматривается как другой тип.
  2. класс значений присваивается массиву.
  3. делать тесты типов во время выполнения, такие как сопоставление с образцом.

Некоторые цитаты из классов значений SIP, которые могут прояснить ваши сомнения:

Классы значений...

  1. ... должен иметь только первичный конструктор с ровно одним открытым параметром val, тип которого не является классом значений.

  2. ... не может быть расширен другим классом.

Согласно пункту 1. оно не может быть абстрактным; на 2. ваша кодировка не работает.

Есть еще одна оговорка:

Класс значений может расширять только универсальные черты и не может сам расширяться. Универсальная черта - это черта, которая расширяет Any, имеет только defs в качестве членов и не выполняет инициализацию. Универсальные черты позволяют базовое наследование методов для классов значений, но они несут накладные расходы при распределении.

Учитывая все это, основываясь на вашем последнем фрагменте, это может сработать:

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

Но имейте в виду, что распределение происходит только в том случае, если вы хотите использовать CatId и DogId в качестве AbstractId. Для лучшего понимания рекомендую прочитать SIP.

Другие вопросы по тегам