Search code examples
scalaimplicitshapelessexistential-typehlist

How to create typeclass with HList existential member


I'm trying to create custom typeclass mimicking shapeless typeclasses. It looks like this:

  trait View[Record] {
    type Result <: HList
    def apply(r: Record): Result
  }

  object View extends LowPriorityLiftFunction1{
    type Aux[Record, L <: HList] = View[Record] {type Result = L}
    implicit def atView[Record: View] = at[Record](implicitly[View[Record]].apply)
  }

Suppose I'm providing it's functionality like this:

object toHView extends ->( (_:Int) + 1)

implicit def provideView[Record, L <: HList]
(implicit generic: Generic.Aux[Record, L],
 mapper: Mapper[toHView.type, L])
: View.Aux[Record, mapper.Out] =
  new View[Record] {
    type Result = mapper.Out

    def apply(r: Record) = mapper(generic.to(r))
  }

So if we define:

case class Viewable(x: Int, y: Int, z : Int)
case class NotViewable(x: Int, y: Long, z : Int)

then

val view = View(Viewable(1, 2, 3)) // is 2 :: 3 :: 4 :: HNil

val noView = View(NotViewable(1, 2, 3)) // is HNil

Trouble here if I try then to acquire

view.head

I'm having

Error:could not find implicit value for parameter c: IsHCons[View[Viewable]#Result]

How could I define this typeclass to effectively use all it's type members later?

Of course i could get rid of type members:

trait View[Record, Result <: HList] {
  def apply(r: Record): Result
}

object View extends LowPriorityLiftFunction1{
  implicit def atView[Record, Result]
  (implicit view: View[Record, Result]) = at[Record](view.apply)
}

object toHView extends ->((_: Int) + 1)
implicit def provideView[Record, L <: HList]
(implicit generic: Generic.Aux[Record, L],
 mapper: Mapper[toHView.type, L])
: View[Record, mapper.Out] =
  new View[Record, mapper.Out] {
    type Result = mapper.Out  

    def apply(r: Record) = mapper(generic.to(r))
  }

but from this point at

val view = View(Viewable(1, 2, 3))

I'm getting an "ambigous implicit values" issue


Solution

  • Ok, here it is: change

    implicit def atView[Record: View] = at[Record](implicitly[View[Record]].apply)
    

    to

    implicit def atView[Record](implicit v: View[Record]) = at[Record](v.apply(_))
    

    The reason is that implicitly loses precision when dealing with refined type members, so instead of expected refined type of your HList (which in this case would be Int :: Int :: Int :: HNil), the compiler spits out a rather useless View#Result.

    Using an implicit parameter instead of a context bound seems to preserve the refined type instead.

    Also, shapeless' the is an alternative to implicitly which preserves type refinements, although it doesn't seem to work in this case.

    Here's an example of implicitly losing precision, taken from the the implementation in shapeless:

    scala> trait Foo { type T ; val t: T }
    defined trait Foo
    
    scala> implicit val intFoo: Foo { type T = Int } = new Foo { type T = Int ; val t = 23 }
    intFoo: Foo{type T = Int} = \$anon\$1@6067b682
    
    scala> implicitly[Foo].t  // implicitly loses precision
    res0: Foo#T = 23
    
    scala> implicitly[Foo].t+13
    <console>:13: error: type mismatch;
      found   : Int(13)
      required: String
                  implicitly[Foo].t+13
                                     ^
    
    scala> the[Foo].t         // the retains it
    res1: Int = 23
    
    scala> the[Foo].t+13
    res2: Int = 36