Shapeless StringLike trait with minimal template
I am trying to define a generic method for creating a type of type string. For example, I would like
case class Foo1(s: String) extends AnyVal
case class Foo2(s: String) extends AnyVal
...
have, say, scalaz.Show, scalaz.Equal, argonaut.CodecJson, etc. copies. I know this is possible with hacky techniques like hijacking the apply / unapply functions generated by the case classes, but I was hoping to create a template-safe, non-formless solution. Here's the best I've come up with:
import scalaz._, Scalaz._
import argonaut._, Argonaut._
import shapeless._
trait HasName[A] {
def to(v: A): String
def fr(v: String): A
}
object HasName {
def apply[A](implicit instance: HasName[A]): HasName[A] = instance
def instance[A](f: A => String, g: String => A): HasName[A] = new HasName[A] { def to(v: A) = f(v); def fr(v: String) = g(v) }
implicit val hlist: HasName[String :: HNil] = new HasName[String :: HNil] {
def to(v: String :: HNil) = v.head
def fr(v: String) = v :: HNil
}
implicit def generic[A, R](implicit F: Generic.Aux[A, R], G: HasName[R]): HasName[A] = instance(
v => G.to(F.to(v)),
v => F.from(G.fr(v))
)
}
trait Name[A] {
val F: HasName[A]
implicit val show: Show[A] = Show.shows(F.to)
implicit val read: Read[A] = Read.readUnsafe(F.fr)
implicit val json: CodecJson[A] = CodecJson[A](v => jString(F.to(v)), c => c.as[String] map F.fr)
implicit val equal: Equal[A] = Equal.equalA[A]
}
Users can then execute
case class Foo1(s: String) extends AnyVal
object Foo1 extends Name[Foo1] {
val F = cachedImplicit[HasName[Foo1]]
}
It's not too many templates, but there is also an annoying F. I tried this:
class Name[A](implicit F: HasName[A]) {
implicit val show: Show[A] = Show.shows(F.to)
implicit val read: Read[A] = Read.readUnsafe(F.fr)
implicit val json: CodecJson[A] = CodecJson[A](v => jString(F.to(v)), c => c.as[String] map F.fr)
implicit val equal: Equal[A] = Equal.equalA[A]
}
which would be better on the call site:
object Foo1 extends Name[Foo1]
but it doesn't work; you cannot have implicit name parameters, and you cannot pass a circular reference not by name.
Any ideas on how to keep the calling and called code nice?
source to share
You can use the fact that yours HasName
will be implicitly in scope to do the following
trait Name[A] {
implicit def show(implicit F: HasName[A]): Show[A] = Show.shows(F.to)
implicit def read(implicit F: HasName[A]): Read[A] = Read.readUnsafe(F.fr)
implicit def json(implicit F: HasName[A]): CodecJson[A] = CodecJson[A](v => jString(F.to(v)), c => c.as[String] map F.fr)
implicit def equal(implicit F: HasName[A]): Equal[A] = Equal.equalA[A]
}
source to share