0

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 :/

kpbochenek
  • 469
  • 2
  • 21

2 Answers2

2

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)
Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
  • I have case classes and objects automatically generated that's why I didn't want to change that. Your solution is nice and clean no doubt. – kpbochenek Jul 31 '16 at 18:02
  • `implicit def IdFn[TC <: AbstractId](to: {def apply(s: String): TC})(implicit ev: GeneratorLike[TC]): SpecGeneratorLike[TC] = new SpecGeneratorLike[TC](ev, to) class GeneratorLike[TC <: AbstractId](ev: PrefixLike[TC], x: {def apply(s: String): TC}) { def generate(): TC = x.apply(ev.prefix + "-" + UUID.randomUUID()) }` What do you think about something like that? – kpbochenek Jul 31 '16 at 18:05
0

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()

Community
  • 1
  • 1
flavian
  • 28,161
  • 11
  • 65
  • 105
  • This one I don't like because you can easily mess up with something like: `ChairId(implicitly[Generator[TableId]]())` I have classes / objects used in production code and simply want to add nice interface for writing tests. One of things I though would be to add possibility to generate ID's and tightly couple prefix with proper case class. Because we read most of our time code I really wanted to provide solution where we can just write `ChairId.generate()` or `TableId.generate()` – kpbochenek Jul 31 '16 at 20:20
  • Hi @kpbochenek I would suggest a very different approach then. Have a look here: https://github.com/outworkers/phantom/blob/develop/phantom-dsl/src/test/scala/com/websudos/phantom/tables/package.scala#L41 – flavian Jul 31 '16 at 21:22
  • @kpbochenek I've improved my solution to give you what you wanted. – flavian Aug 30 '16 at 02:43