Search code examples
shapeless

How to create overloads for Product and Record?


Since they are both of type HList, I have to create functions with different names. Here is an example as in kittens

 //sequence a product HList
 trait Sequencer[L <: HList] {
  type Out
  def apply(in: L): Out
}

//sequence an extensible record HList
trait RecordSequencer[L <: HList] {
  type Out
  def apply(in: L): Out
}

implicit class sequenceOps[L <: HList](self: L) {
    def sequence(implicit seq: Sequencer[L]): seq.Out = seq(self)
    def sequenceRecord(implicit seq: RecordSequencer[L]): seq.Out = seq(self) //version for extensible records
}

Note here I had to use the name sequenceRecord to be different from the sequence method for product HList. I found it quite arbitrary and cumbersome. Is there a way to have both extensible record and product HList having the sequence method with the same name? Or do you always have to provide two different names for these two kinds of HList?


Solution

  • You can use the existence of an instance of shapeless.ops.record.Keys for L to discriminate between record and non-record HLists. Then the rest of the job is to juggle implicit priorities to ensure that the two extension methods don't conflict. Something like the following will do the job,

    import shapeless._, ops.record._, syntax.singleton._
    
    //sequence a product HList
    trait Sequencer[L <: HList] {
      type Out
      def apply(in: L): Out
    }
    
    object Sequencer {
      implicit def mkSequencer[L <: HList]:
        Sequencer[L] { type Out = String } =
          new Sequencer[L] {
            type Out = String
            def apply(l: L): String = "Sequencer"
          }
    }
    
    //sequence an extensible record HList
    trait RecordSequencer[L <: HList] {
      type Out
      def apply(in: L): Out
    }
    
    object RecordSequencer {
      implicit def mkRecordSequencer[L <: HList]:
        RecordSequencer[L] { type Out = String } =
          new RecordSequencer[L] {
            type Out = String
            def apply(l: L): String = "RecordSequencer"
          }
    }
    
    // Syntax for non-records
    class SOps[L <: HList](self: L) {
      def sequence(implicit seq: Sequencer[L]): seq.Out = seq(self)
    }
    
    // Syntax for records
    class RSOps[L <: HList](self: L) {
      def sequence(implicit seq: RecordSequencer[L]): seq.Out = seq(self)
    }
    
    object sequence extends lpSequence {
      // Keys instance only available for records
      implicit def mkRSOps[L <: HList](l: L)
        (implicit keys: Keys[L]): RSOps[L] = new RSOps(l)
    }
    
    trait lpSequence {
      // fallback for non-records
      implicit def mkSOps[L <: HList](l: L): SOps[L] = new SOps(l)
    }
    
    object Demo extends App {
      import sequence._
    
      val l = 23 :: "foo" :: true :: HNil
      val r = 'a ->> 23 :: 'b ->> "foo" :: 'c ->> true :: HNil
    
      assert(l.sequence == "Sequencer")
      assert(r.sequence == "RecordSequencer")
    }