I know using Shapeless I can do something like this:
import shapeless._, syntax.singleton._, record._
case class Foo(x: Int, y: String)
case class RichFoo(x: Int, y: String, z: Double)
def makeRich(foo: Foo): RichFoo = {
val x = ('z ->> 0.9)
val repr = LabelledGeneric[Foo].to(foo) + x
LabelledGeneric[RichFoo].from(repr)
}
val a = Foo(1, "hello")
val b = makeRich(a)
Now I want to write a generic way to do this:
trait Morph[A, B, AR, BR] {
def apply(a: A)(f: AR => BR): B
}
object Morph {
implicit def genericMorph[A, B, AR, BR](implicit genA: LabelledGeneric.Aux[A, AR], genB: LabelledGeneric.Aux[B, BR]): Morph[A, B, AR, BR] =
new Morph[A, B, AR, BR] {
override def apply(a: A)(f: AR => BR) = genB.from(f(genA.to(a)))
}
implicit class Syntax[A](a: A) {
def morph[AR, BR, B](f: AR => BR)(implicit morph: Morph[A, B, AR, BR]): B =
morph(a)(f)
}
}
But, now the usage is unweidly?
val a = Foo(1, "hello")
a.morph[???, ???, RichFoo](_ + ('z ->> 0.9))
What is a better way to design this API?
I tried something like this:
implicit class Syntax[A](a: A) {
def morphTo[B] = new {
def using[AR <: HList, BR <: HList](f: AR => BR)(implicit morph: Morph[A, B, AR, BR]): B =
morph(a)(f)
}
}
a.morphTo[RichFoo].using(_ :+ ('z ->> 0.9))
But it does not really work
There are two restrictions that prevent type inference from working the way you want in your example (both have nothing to do with shapeless btw):
In current
scalac
specifying type parameters explicitly is all or nothing. But you want to specify onlyB
leaving the rest to be inferred. Currying is one solution to this problem. So your attempt was on the right track, but didn't account for 2.Type inference for method parameters flows from left to right one parameter list at a time. But you want to infer the type of
f
based on the type ofmorph
which comes last because it is implicit. The solution here is... Currying again.So from 1. and 2. it follows that you must curry twice:
There is an alternative solution to 1. - use a dummy argument to specify the type parameter
B
:For future reference on how these issues are addressed in Dotty:
a.morph[B = RichFoo]
Edit: Usually it's a good idea to define types that depend on other types as type members: