Множественное наследование класса значений 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
.. использование класса значений не подходит для вашего случая, потому что сам класс все равно будет выделен. Смотрите сводку распределения:
Класс значения фактически создается, когда:
- класс значения рассматривается как другой тип.
- класс значений присваивается массиву.
- делать тесты типов во время выполнения, такие как сопоставление с образцом.
Некоторые цитаты из классов значений SIP, которые могут прояснить ваши сомнения:
Классы значений...
... должен иметь только первичный конструктор с ровно одним открытым параметром val, тип которого не является классом значений.
... не может быть расширен другим классом.
Согласно пункту 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.