trait Iso[A, B] { def to(a: A): B def from(b: B): A } trait Derivable[F[_]] { type Fields[_] def fieldOf[A](name: String, value: F[A]): Fields[A] def fieldUnit: Fields[Unit] def fieldZip[A, B](left: Fields[A], right: Fields[B]): Fields[(A, B)] def fieldMap[A, B](fa: Fields[A])(f: Iso[A, B]): Fields[B] type Cases[_] def caseOf[A](name: String, fields: Fields[A]): Cases[A] def caseUnit: Cases[Nothing] def caseZip[A, B](left: Cases[A], right: Cases[B]): Cases[Either[A, B]] def caseMap[A, B](fa: Cases[A])(f: Iso[A, B]): Cases[B] def coproduct[A](name: String, cases: Cases[A]): F[A] def product[A](name: String, fields: Fields[A]): F[A] } trait Show[A] { def show(a: A): String } val showDerivable: Derivable[Show] = new Derivable[Show] { type Fields[A] = A => List[String] def fieldOf[A](name: String, value: Show[A]): Fields[A] = a => List(name + "=" + value.show(a)) def fieldUnit: Fields[Unit] = a => Nil def fieldZip[A, B](left: Fields[A], right: Fields[B]): Fields[(A, B)] = { case (a, b) => left(a) ++ right(b) } def fieldMap[A, B](fa: Fields[A])(f: Iso[A, B]): Fields[B] = b => fa(f.from(b)) type Cases[A] = A => String def caseOf[A](name: String, fields: Fields[A]): Cases[A] = a => name + "(" + fields(a).mkString(", ") + ")" def caseUnit: Cases[Nothing] = a => a def caseZip[A, B](left: Cases[A], right: Cases[B]): Cases[Either[A, B]] = ab => ab.fold(left, right) def caseMap[A, B](fa: Cases[A])(f: Iso[A, B]): Cases[B] = b => fa(f.from(b)) def coproduct[A](name: String, cases: Cases[A]): Show[A] = a => cases(a) def product[A](name: String, fields: Fields[A]): Show[A] = a => name + "(" + fields(a).mkString(", ") + ")" } implicit val intShow: Show[Int] = a => a.toString implicit val boolShow: Show[Boolean] = a => a.toString final case class Foo(i: Int, b: Boolean) val fooShow = { import showDerivable._ val fooIso = new Iso[(Int, Boolean), Foo] { def to(p: (Int, Boolean)): Foo = Foo(p._1, p._2) def from(p: Foo): (Int, Boolean) = (p.i, p.b) } product("Foo", fieldMap(fieldZip( fieldOf("i", implicitly[Show[Int]]), fieldOf("b", implicitly[Show[Boolean]])))(fooIso)) } println(fooShow.show(Foo(1, true))) ///