Lifeless: Checking the type constraints of polymorphic functions (raised types)
I am writing an internal DSL and I am using Shapeless for type safety. However, I am stuck with the problem.
A simplified version of the problem is as follows.
Consider the code snippet below:
import shapeless._
import syntax.std.function._
import ops.function._
implicit class Ops[P <: Product, L <: HList](p: P)(implicit val gen: Generic.Aux[P, L]) {
def ~|>[F, R](f: F)(implicit fp: FnToProduct.Aux[F, L β R]) =
f.toProduct(gen.to(p))
}
(1, 2) ~|> ((x: Int, y: Int) β x + y) // -> at compile time, it ensures the types are aligned.
(1, 2, 3) ~|> ((x: Int, y: Int, z: Int) β x + y + z) // -> compiles okay
(1, "2", 3) ~|> ((x: Int, y: Int, z: Int) β x + y + z) // -> gives a compile error, as expected.
However, instead of A, I would like to use the container type Place[A]
.
case class Place[A](a: A)
val a = Place[Int](1)
val b = Place[Int]("str")
and also provides type alignment with respect to type parameters.
(a, b) ~|> ((x: Int, y: String) β x.toString + y)
That is, in the above case, I would like the types to be checked based on the type parameter Place [_], which is in the above case, Int
and String
accordingly.
I appreciate your help!
source to share
You can do this with a combination of Unwrapped
and LiftAll
.
Unwrapped
allows you to retrieve content AnyVal
, LiftAll
calls the type class for each element HList
. If I understand correctly what you are trying to do, it might look like this:
case class Place[A](a: A) extends AnyVal // cuz why not?
implicit class Ops[P <: Product, L <: HList, U <: HList, C <: HList](p: P)
(implicit
val g: Generic.Aux[P, L],
val l: LiftAll.Aux[Unwrapped, L, U],
val c: Comapped.Aux[Place, L, C]
) {
def ~|>[F, R](f: F)(implicit fp: FnToProduct.Aux[F, C β R]) = {
def fuckitZip(l1: HList, l2: HList): HList = (l1, l2) match {
case (h1 :: t1, h2 :: t2) => (h1, h2) :: fuckitZip(l1, l2)
case _ => HNil
}
def fuckitMap(l: HList, f: Any => Any): HList = l match {
case HNil => HNil
case h :: t => f(h) :: fuckitMap(t, f)
}
def f(a: Any): Any = a match {
case (x: Any, y: Any) =>
x.asInstanceOf[Unwrapped[Any] { type U = Any }].unwrap(y)
}
val zp = fuckitZip(g.to(p), l.instances)
val uw = fuckitMap(zp, f _).asInstanceOf[C]
f.toProduct(uw)
}
}
Note that I've also used an Comapped
insecure postcard here to simplify it, making it typical with proper HList
zip / map left as an exercise.
As usual with these complex conversions, it might be easier (and faster / compiler) to override everything with a dedicated type class, nesting everything, I just wanted to show that this is doable with primitive operations :)
source to share