简体   繁体   中英

scala type parameter as object type

How can I achieve this:

final case class ChairId(id: String)

trait GeneratorLike[TO, TC <: AbstractId] {
  val prefix: String
  def generate(): TC = TO.apply(prefix + "-" + UUID.randomUUID())
}

implicit object ChairIdGenerator extends GeneratorLike[ChairId.type, ChairId] {
  val prefix: String = "CHAIR"
}

implicit def IdFn[TO, TC <: AbstractId](x: TO)(implicit ev: GeneratorLike[TO, TC]): GeneratorLike[TO, TC] = ev

//right now I can call:
ChairId.generate()

I don't want to define companion object for that situation and I wondered if there is a chance to extend object with use of implicits?

When I do (I use TO as TypeObject and TC as TypeClass naming) idFn[TO, TC] I want TO to be object that implements def apply(id: String): TC can I enforce that? And how would I get to use this function? It feels totally impossible to call function on type parameter :/

It is impossible to call a method on a type parameter, because it represents a type and not an object . You can call a method on an object, because it is something that exists, but a type is an abstract concept. I don't know what your motivation is for wanting to implicitly add generate() to companion objects, because it actually requires just as much code to define an implicit GeneratorLike than it does to define the companion for ChairId .

If you force GeneratorLike to have an apply method (which can be implemented by case class apply ), and remove the first type parameter, this will work.

trait GeneratorLike[TC <: AbstractId] { this: Singleton =>
  val prefix: String
  def apply(id: String): TC
  def generate(): TC = apply(prefix + "-" + UUID.randomUUID())
}

abstract class AbstractId

final case class ChairId(id: String) extends AbstractId

object ChairId extends GeneratorLike[ChairId] {
  val prefix = "CHAIR"
}

scala> ChairId.generate()
res0: ChairId = ChairId(CHAIR-60bb01c7-af95-46c7-af45-0b3fa78b3080)

Structural typing is not a particularly good idea on the JVM, so always try to avoid the def test(x: {def apply(s: String)}): TC type stuff because it is implemented using reflection which can be a dog performance wise.

Second, you should probably avoid using val inside a trait . Read here .

The approach you have considered is actually the right one, and namely type classes.

trait HasGenerator[T] {
  def apply(uuid: String): T 
  def generate[T : Generator] = apply(Generator[T].generate)
}

final case class ChairId(id: String) 

object ChairId extends HasGenerator[ChairId]

trait Generator[TO] {
  def prefix: String
  def generate(): String = prefix + "-" + UUID.randomUUID()
  def apply(): String = generate
}

object Generator {
  def apply[T : Generator] = implicitly[Generator[T]]
}

// Notice .type is not necessary
implicit object ChairIdGenerator extends Generator[ChairId] {
  override def prefix = "CHAIR"
}

Why not just use:

ChairId(Generator[ChairId])

This all seems like overkill though so you can quite easily somehow. It's worth fleshing out your requirements a bit more because type classes don't really seem super necessary just yet. You could just do with:

Update

If you use something like the HasGenerator that I have added above in conjunction with the companion object, you can now successfully call ChairId.generate()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM