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