Возвращает case-класс, который расширяет трейт

Мне нужно определить, чтобы возвращать класс case, который расширяет черту:

trait Id {
  def id: Long
}

case class Person(name: String)

val john = Person("John") 
val johnWithId: Person with Id = /*person -> 123L*/ ???

Любая идея, как я могу этого достичь?

Я пытаюсь уменьшить дублирование кода, поэтому я не объявлял трейт Person следующим образом:

trait Id {
  def id: Long
}

trait Person {
  def name: String
}

case class PersonWithoutId(name: String) extends Person

case class PersonWithId(name: String, id: Long) extends Person with Id

val john = PersonWithoutId("John")
val johnWithId: Person with Id = PersonWithId(person.name, 123L)

Есть идеи, как этого добиться?


person Maxence Cramet    schedule 27.07.2018    source источник


Ответы (4)


Немного окольным путем:

case class WithId[A](id: Long, value: A) extends Id
object WithId {
  implicit def getValue[A](x: WithId[A]): A = x.value
}

// elsewhere
val johnWithId = WithId(123, john)

johnWithId не расширяет Person (так что, например, johnWithId.isInstanceOf[Person] является ложным), но все же может использоваться там, где ожидается Person и getValue.

person Alexey Romanov    schedule 28.07.2018
comment
Я уже использовал что-то подобное, и мне просто не хватало неявного определения, которое избегало бы использовать .value каждый раз. Поскольку я использовал много классов WithXXX, он будет работать только на первом sublevel, но, по крайней мере, немного уменьшит многословие. - person Maxence Cramet; 29.07.2018

Когда экземпляр Person уже создан, уже слишком поздно — вы не можете изменить экземпляр john после того, как он уже создан. Однако вы можете создать экземпляр Person with Id:

val johnWithId: Person with Id = new Person("John") with Id {
  override def id: Long = 123L
}

Обратите внимание, что на самом деле это не эквивалентно использованию класса case PersonWithId(name: String, id: Long), например — equals и hashcode будут игнорировать идентификатор в этой реализации.

person Tzach Zohar    schedule 27.07.2018

Самым правильным решением будет

case class Person(name: String, id: Long) extends Id

в то время как вы могли бы в теории сделать

val johnWithId: Person with Id = new Person("John") with Id { def id = 123L }

как заявил Андрей, это не будет соблюдать общий контракт case class, потому что люди с разными идентификаторами и одинаковыми именами будут равны, поскольку id не будет использоваться в методе equals

person pedrorijo91    schedule 28.07.2018
comment
Объект, созданный во второй строке, действительно соответствует тому же case class контракту, что и раньше: новых case class определений нет, и ни одно из equals/hashCode/toString не переопределяется, поэтому во время hashCode вычислений будут учитываться только аргументы конструктора и т. д. Никаких чудес от этого кода я не ждал, поэтому не удивился, когда чуда не произошло... - person Andrey Tyukin; 28.07.2018

person    schedule
comment
Обратите внимание, что на самом деле это не эквивалентно использованию класса case PersonWithId(name: String, id: Long), например, - equals и hashCode будут игнорировать идентификатор в этой реализации (два экземпляра с разными идентификаторами будут равны) - person Tzach Zohar; 27.07.2018
comment
@TzachZohar Не могли бы вы просто восстановить свой ответ? - person Andrey Tyukin; 28.07.2018