Search code examples
scalashapeless

Filter a HList using a supertype


import shapeless._
import shapeless.labelled._
import shapeless.tag._

Given a HList like

case class Foo(a: String, b: Int)

val hlist = LabelledGeneric[Foo].to(Foo("Hello", 42))

and a Witness like

val aW = Witness("a")

I expect the two expressions

hlist.filter[String with KeyTag[Symbol with Tagged[aW.T], String]]
hlist.filter[KeyTag[Tagged[aW.T], String]]

to yield the same result type. Unfortunately, the second expression infers to HNil.

Why is that? How can I filter on supertypes like this?


Solution

  • Type class Filter is invariant, data type KeyTag is invariant with respect to key type. So this can't work with a supertype. implicitly[Filter.Aux[Int :: String :: Boolean :: HNil, AnyVal, Int :: Boolean :: HNil]] and implicitly[Filter.Aux[Int :: String :: Boolean :: HNil, AnyRef, String :: HNil]] don't compile, implicitly[Filter.Aux[Int :: String :: Boolean :: HNil, AnyVal, HNil]] and implicitly[Filter.Aux[Int :: String :: Boolean :: HNil, AnyRef, HNil]] compile instead.

    You should use Filter as follows

    implicitly[Filter.Aux[Record.`'a -> String, 'b -> Int`.T, FieldType[Symbol @@ "a", String], Record.`'a -> String`.T]]
    implicitly[Filter.Aux[Record.`'a -> String, 'b -> Int`.T, FieldType[Symbol @@ aW.T, String], Record.`'a -> String`.T]]
    
    hlist.filter[FieldType[Witness.`'a`.T, String]] // Hello :: HNil
    hlist.filter[FieldType[Symbol @@ Witness.`"a"`.T, String]] // Hello :: HNil
    hlist.filter[FieldType[Symbol @@ "a", String]] // Hello :: HNil
    hlist.filter[FieldType[Symbol @@ aW.T, String]] // Hello :: HNil
    hlist.filter[FieldType[Symbol with Tagged[aW.T], String]] // Hello :: HNil
    hlist.filter[String with KeyTag[Symbol with Tagged[aW.T], String]] // Hello :: HNil
    

    If you want to filter by a supertype then you should use Collect with properly defined Poly.

    trait SuperPoly[Upper] extends Poly1
    object SuperPoly {
      implicit def cse[P <: SuperPoly[Upper], Upper, A](implicit
        unpack: Unpack1[P, SuperPoly, Upper],
        ev: A <:< Upper
      ): poly.Case1.Aux[P, A, A] = poly.Case1(identity)
    }
    
    implicitly[Collect.Aux[Int :: String :: Boolean :: HNil, SuperPoly[AnyVal], Int :: Boolean :: HNil]]
    implicitly[Collect.Aux[Int :: String :: Boolean :: HNil, SuperPoly[AnyRef], String :: HNil]]
    
    implicitly[Collect.Aux[Record.`'a -> String, 'b -> Int`.T, SuperPoly[KeyTag[_ <: Tagged[aW.T], String]], Record.`'a -> String`.T]]
    val aPoly = new SuperPoly[KeyTag[_ <: Tagged[aW.T], String]] {}
    implicitly[Collect.Aux[Record.`'a -> String, 'b -> Int`.T, aPoly.type, Record.`'a -> String`.T]]
    
    hlist.collect(aPoly) // Hello :: HNil