Search code examples
scalashapeless

Usage of enclosing generic type parameters in nested polymorphic values


Is it possible to rewrite the following example using only polymorphic functions without TypeTags? The example consists of a class A[T] which has a method matches returning true when applied to an instance of A with the same type parameter T and false if this type parameter has a different value. Then matches is mapped over an hlist l of A[T] twice resulting in an hlist of nested hlists containing results of matching each item of l against others:

import scala.reflect.runtime.universe._
import shapeless._

class A[T: TypeTag]{
  object matches extends Poly1 {
    implicit def default[U: TypeTag] = at[A[U]]{ _ => typeOf[U] <:< typeOf[T] }
  }
}

val l = new A[Int] :: new A[String] :: new A[Boolean] :: HNil

object matcher extends Poly1 {
  implicit def default[T] = at[A[T]]{ a => l map a.matches }
}

l map matcher

Each item has one match, i.e. the result is:

(true :: false :: false :: HNil) ::
(false :: true :: false :: HNil) ::
(false :: false :: true :: HNil) :: HNil

When I try to rewrite the example without TypeTags, matches always uses it's no case and return false:

import shapeless._

class A[T]{
    object matches extends Poly1 {
        implicit def yes = at[A[T]]{_ => true}
        implicit def no[U] = at[U]{_ => false}
    }
}

val l = new A[Int] :: new A[String] :: new A[Boolean] :: HNil

object matcher extends Poly1 {
    implicit def default[T] = at[A[T]]{ a => l map a.matches }
}

l map matcher

The result is:

(false :: false :: false :: HNil) ::
(false :: false :: false :: HNil) ::
(false :: false :: false :: HNil) :: HNil

Is it possible to rewrite this example without TypeTags and get the same result as in the first case?


Solution

  • It looks like you really want to be able to partially apply higher rank functions to solve this problem cleanly. This isn't possible with any kind of nice syntax out of the box, but I once wrote some code to help make it a little easier (note that this is all 1.2.4):

    import shapeless._
    
    trait ApplyMapper[HF, A, X <: HList, Out <: HList] {
      def apply(a: A, x: X): Out
    }
    
    object ApplyMapper {
      implicit def hnil[HF, A] = new ApplyMapper[HF, A, HNil, HNil] {
        def apply(a: A, x: HNil) = HNil
      }
      implicit def hlist[HF, A, XH, XT <: HList, OutH, OutT <: HList](implicit
        pb: Poly.Pullback2Aux[HF, A, XH, OutH],
        am: ApplyMapper[HF, A, XT, OutT]
      ) = new ApplyMapper[HF, A, XH :: XT, OutH :: OutT] {
        def apply(a: A, x: XH :: XT) = pb(a, x.head) :: am(a, x.tail)
      }
    }
    

    See the answer linked above for some context.

    This allows you to write the following:

    class A[T]
    
    object matches extends Poly2 {
      implicit def default[T, U](implicit sub: U <:< T = null) =
        at[A[T], A[U]]((_, _) => Option(sub).isDefined)
    }
    
    object mapMatcher extends Poly1 {
       implicit def default[T, X <: HList, Out <: HList](
         implicit am: ApplyMapper[matches.type, A[T], X, Out]
       ) = at[(A[T], X)] { case (a, x) => am(a, x) }
    }
    
    val l = new A[Int] :: new A[String] :: new A[Boolean] :: HNil
    

    There may be a nicer solution, but at the moment I only have a minute to respond, and it works as is:

    scala> l.zip(l mapConst l).map(mapMatcher).toList.foreach(println)
    true :: false :: false :: HNil
    false :: true :: false :: HNil
    false :: false :: true :: HNil
    

    As desired.