I'm trying to make this rather silly example work, planning to then extend it to something more meaningful.
But no luck so far: I get could not find implicit value for parameter ihc
What am I missing?
sealed trait Field[T] { def name: String }
case class IntegerField(name: String) extends Field[Int]
val year = IntegerField("year")
val test = (year :: 23 :: HNil) :: (year :: 2 :: HNil) :: HNil
type TypedMap = IntegerField :: Int :: HNil
def get[L <: HList : <<:[TypedMap]#λ]
(key: IntegerField, list: L)
(implicit ihc: IsHCons.Aux[L, TypedMap, L]
): Option[Int] = {
if( list == HNil ) return None
val elem: TypedMap = list.head
if( elem.head == key ) Some(elem.tail.head)
else get(key, list.tail)
}
get(year, test)
When you write IsHCons.Aux[L, TypedMap, L]
you're asking for evidence that the hlist L
has head TypedMap
and tail L
, which would mean that it's an infinite hlist, which isn't possible, since Scala doesn't allow this kind of arbitrarily recursive type (try writing something like type Foo = Int :: Foo
, for example—you'll get a "illegal cyclic reference" error). It's also probably not what you want.
In general you're unlikely to use IsHCons
much in Shapeless, since it's almost always better just to indicate the structure you want in the type. For example, the following two definitions do the same thing:
import shapeless._, ops.hlist.IsHCons
def foo[L <: HList](l: L)(implicit ev: IsHCons[L]) = ev.head(l)
And:
def foo[H, T <: HList](l: H :: T) = l.head
But the second is obviously preferable (it's clearer, it doesn't require an extra type class instance to be found at compile time, etc.), and it's almost always possible to write whatever you're trying to do that way.
Also note that requiring an IsHCons
instance means that the recursion here won't work as stated—you can't call get
on an HNil
, since the compiler can't prove that it's an HCons
(because it's not).
Are you sure you need an hlist at all? If you're requiring that all members of the hlist are of type TypedMap
, you might as well use Shapeless's Sized
(if you want the type to capture the length) or even just a plain old List
.
If you really, really want to use an HList
here, I'd suggest writing a new type class:
trait FindField[L <: HList] {
def find(key: IntegerField, l: L): Option[Int]
}
object FindField {
implicit val findFieldHNil: FindField[HNil] = new FindField[HNil] {
def find(key: IntegerField, l: HNil) = None
}
implicit def findFieldHCons[H <: TypedMap, T <: HList](implicit
fft: FindField[T]
): FindField[H :: T] = new FindField[H :: T] {
def find(key: IntegerField, l: H :: T) = if (l.head.head == key)
Some(l.head.tail.head)
else fft.find(key, l.tail)
}
}
def get[L <: HList](key: IntegerField, l: L)(implicit
ffl: FindField[L]
): Option[Int] = ffl.find(key, l)
And then:
scala> get(IntegerField("year"), test)
res3: Option[Int] = Some(23)
scala> get(IntegerField("foo"), test)
res4: Option[Int] = None
This is a pretty common pattern in Shapeless—you inductively describe how to perform an operation on an empty hlist, then on an hlist with a head prepended to a tail that you know how to perform the operation on, 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