Search code examples
scalashapeless

When is an empty HList not an HList?


While learning shapeless, I wonder, why this doesn't compile:

def someHList[H <: HList]: H = HNil

since the HNil object extends the HNil trait which extends HList?

What is the right way to define a method in a trait which returns some HList, which is only implemented in by the extending class?

I'd like to do something like the following:

trait Parent {
  def someHList[H <: HList]: H
}

object Child1 extends Parent {
  def someHList[H <: HList] = HNil
}

object Child2 extends Parent {
  def someHList[H <: HList] = 1 :: "two" :: HNil
}

Any advice is appreciated. Thanks!

EDIT

To elaborate as I realize what I underspecified in my original question:

1.) It is desirable to not have to specify H explicitly in each implementing class, but rather let it be inferred (at the call site?).

2.) I'd like to use HNil as a default implementation in the parent trait which can optionally be overridden in subclasses. My example probably should have been:

trait Parent {
  def someHList[H <: HList]: H = HNil
}

object Child extends Parent {
  override def someHList[H <: HList] = 1 :: "two" :: HNill
}

Solution

  • HNil object is an HList. But it is not necessary H.

    definition like

    def someHList[H <: HList]: H = HNil
    

    should be read as

    for any type H, subtype of HList there is a way to construct it's member and it will be HNil

    which is obviously erroneous

    Thing you are trying to do, as I feel, is rephrased version of it to

    there is a type H, subtype of HList and a way to construct it's member

    If so you can use type member like this:

    import shapeless._
    
    trait Parent {
      type H <: HList
      def someHList: H
    }
    
    object Child1 extends Parent {
      type H = HNil
      def someHList: H = HNil
    }
    
    object Child2 extends Parent {
      type H = Int :: String :: HNil
      def someHList: H = 1 :: "two" :: HNil
    }
    

    Update

    You can also refactor it a little bit to make some types infer automatically like

    abstract class Parent[H <: HList](val someList: H)
    object Child1 extends Parent(HNil: HNil)
    object Child2 extends Parent(1 :: "two" :: HNil)
    

    You may note that type for HNil is set manually, that is because type of object HNil is a HNil.typesubtype of HNil, which can lead the compiler wrong way sometimes