Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Shapeless: IsHCons, implicit not found

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)
like image 542
kenshin Avatar asked Mar 29 '15 15:03

kenshin


1 Answers

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.

like image 127
Travis Brown Avatar answered Nov 17 '22 15:11

Travis Brown