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!

+3


source to share


1 answer


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 :)

+2


source







All Articles