Richard Whaling Richard Whaling - 1 month ago 19
Scala Question

How to infer inner type of Shapeless record value with unary type constructor?

I'm having trouble understanding the way the Shapeless record Selector interacts with scala's type inference. I'm trying to create a method that can grab a field from a Shapeless record by key, only if the value of the field has a certain unary type constructor, in this particular case

Vector[_]
, and then grab an inner value of inferred type
V
out of the
Vector
, in this case with
Vector.apply()
.

I feel like I'm close. This works, with a concrete inner type of
Int
:

val record = ( "a" ->> Vector(0,2,4) ) :: ( "b" ->> Set(1,3,5) ) :: HNil

def getIntFromVectorField[L <: HList](l: L, fieldName:Witness, index:Int)(implicit
sel: Selector.Aux[L, fieldName.T, Vector[Int]]
):Int = l(fieldName).apply(index)

getIntFromVectorField(record,"a",1) // Returns 1
getIntFromVectorField(record,"b",0) // Does not compile, as intended


But if I try to infer the inner type, it fails:

def getValueFromVectorField[L <: HList,V](l:L, fieldName:Witness, index:Int)(implicit
sel: Selector.Aux[L,fieldName.T,Vector[V]]
):V = l(fieldName).apply(index) // Compiles
getValueFromVectorField(record,"a",1) // Why does this not compile?


Here's the full error:

could not find implicit value for parameter sel:
shapeless.ops.record.Selector[shapeless.::[scala.collection.immutable.Vector[Int]
with shapeless.labelled.KeyTag[String("a"),scala.collection.immutable.Vector[Int]],
shapeless.::[scala.collection.immutable.Set[Int]
with shapeless.labelled.KeyTag[String("b"),scala.collection.immutable.Set[Int]],
shapeless.HNil]],String("a")]{type Out = scala.collection.immutable.Vector[V]}


What I have been able to do instead is this:

def getValueFromVectorField[L <: HList,T,V](l:L, fieldName:Witness, index:Int)(implicit
sel: Selector.Aux[L,fieldName.T,T],
unpack: Unpack1[T,Vector,V]
):V = l(fieldName) match {
case v:Vector[V] => v.apply(index)
}
getValueFromVectorField(record,"a",1) // Returns 1, Yay!
getValueFromVectorField(record,"b",0) // Does not compile, as intended


Which should be safe, yes? But the pattern matching doesn't feel very idiomatic for shapeless, and I'm wondering why the more concise approach with inference doesn't work. Is there a cleaner way to do this?

Answer

Scala is really bad about type inference in cases like this (where you want to unify the result of a functional dependency and something like Vector[V] and have the V inferred).

You can help the compiler through the process by breaking down the steps:

import shapeless._, ops.record.Selector, syntax.singleton._

def getValueFromVectorField[L <: HList, VS, V](
  l: L,
  fieldName: Witness,
  index: Int
)(implicit
  sel: Selector.Aux[L, fieldName.T, VS],
  ev: VS <:< Vector[V]
): V = sel(l).apply(index)

val record = ( "a" ->> Vector(0,2,4) ) :: ( "b" ->> Set(1,3,5) ) :: HNil

getValueFromVectorField(record,"a",1) // Returns 1, Yay!
getValueFromVectorField(record,"b",0) // Does not compile, as intended

Now it'll infer VS first and then figure out that VS is a subtype of Vector[V], instead of having to do both in one step.

This is exactly the same thing that your Unpack1 version does, except that Unpack1 only proves that T is Vector[V]—it doesn't actually give you a way to get a Vector[V] from a T (unlike <:<, which does).

So your Unpack1 version is safe, in the sense that you can convince yourself that it provides all the pieces of evidence you need, but they're not in a form the compiler understands, so you have to downcast with the type case in the pattern match. <:< is better because the compiler does understand it, but also because it's more recognizable as a workaround for this limitation, since it's provided by the standard library, etc.