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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With