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?

+3


source to share


1 answer


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]
}

      

+1


source







All Articles