2016-08-22 75 views
1

我正在寫一個通用類型類(用於DynamoDB的編解碼器)以無形式派生代碼。我有一個不使用案例類字段名稱的版本,完全基於類的字段順序與DynamoDB響應中的屬性順序相匹配的想法。它使用Generic和通常的deriveHNil,deriveHCons方法,例如在此描述:https://meta.plasm.us/posts/2015/11/08/type-classes-and-generic-derivation/如何從無形記錄中爲通用類型類派生提取標籤

現在我想要一個使用字段名稱查找相關DynamoDB屬性的版本。我目前的想法是大部分重用先前(基於訂單)版本的方法,並且另外編譯器通過LabelledGenericshapeless.ops.record.Keys提供字段名稱。但是我堅持如何正確使用Keys功能。

的想法是這樣的:功能hconsDecoder應做一次兩件事情:解構HList運行在它的頭+尾decode操作,同時還提取從上述頭的標籤。 LabelledGeneric應提供HList與字段上的標籤,以便H類型參數在hconsDecoder將是記錄中的條目,包含相關信息。但是因爲Keys只能在HList上工作,所以我創建了一個單例H :: HNil來運行Keys

這裏是我的代碼部分:

trait FieldDecoder[A] { 
    def decode(a: AttributeValue): Option[A] 
} 

trait RecordDecoder[A] { 
    def decode(s: Seq[Attribute]): Option[A] 
} 

object RecordDecoderInstances { 

    implicit val hnilDecoder = new RecordDecoder[HNil] { 
    override def decode(s: Seq[Attribute]): Option[HNil] = { 
     Some(HNil) 
    } 
    } 

    object toName extends Poly1 { 
    implicit def keyToName[A] = at[Symbol with A](_.name) 
    } 

    implicit def hconsDecoder[H: FieldDecoder, T <: HList: RecordDecoder](
     implicit kk: Keys[H :: HNil]#Out, 
     m: Mapper[toName.type, Keys[H :: HNil]#Out]) = 
    new RecordDecoder[H :: T] { 
     override def decode(s: Seq[Attribute]): Option[H :: T] = { 

     val attrName = (kk map toName).head.asInstanceOf[String] // compile error here 

     for { 
      h <- implicitly[FieldDecoder[H]] 
      .decode(s.filter(_.name == attrName).head.value) 
      t <- implicitly[RecordDecoder[T]] 
      .decode(s.filterNot(_.name == attrName)) 
     } yield h :: t 

     } 
    } 
} 

鑑於此代碼,編譯器錯誤是:could not find implicit value for parameter c: shapeless.ops.hlist.IsHCons[m.Out]。我已經嘗試了不同的版本,總是面臨着implicit not found錯誤的一些變化。底線是,由於某種原因,Keys不適用於H :: HNil結構。

這是我對Shapeless的第一次認真的嘗試,我不知道我是否以正確的方式。對於這個特殊的錯誤和我的方法,我會很感激。

回答

1

我以前面臨同樣的問題,但沒有找到簡單的方法來遞歸地對齊KeysHListGenericHList。我希望有人會發布更好的解決方案。

一個簡單的解決方案是在遞歸處理之前將輸入序列與鍵對齊。爲了清楚起見,我已將seq處理與通用HList表示法的處理分開。

case class AttributeValue(value: String) 
case class Attribute(name: String, value: AttributeValue) 

trait FieldDecoder[T] { 
    def decode(a: AttributeValue): Option[T] 
} 

HList解碼器:

trait HListDecoder[A <: HList] { 
    def decode(s: Seq[Attribute]): Option[A] 
} 

object HListDecoder { 

    implicit val hnilDecoder = new HListDecoder[HNil] { 
    override def decode(s: Seq[Attribute]): Option[HNil] = { 
     Some(HNil) 
    } 
    } 

    implicit def hconsDecoder[H, T <: HList, LR <: HList](
    implicit 
     fieldDecoder: FieldDecoder[H], 
     tailDecoder: HListDecoder[T]) = 
    new HListDecoder[H :: T] { 
     override def decode(s: Seq[Attribute]): Option[H :: T] = { 
     for { 
      h <- fieldDecoder.decode(s.head.value) 
      t <- tailDecoder.decode(s.tail) 
     } yield h :: t 

     } 
    } 
} 

案例類解碼器:

trait RecordDecoder[A] { 
    def decode(s: Seq[Attribute]): Option[A] 
} 

object RecordDecoder { 

    object toName extends Poly1 { 
    implicit def keyToName[A] = at[Symbol with A](_.name) 
    } 

    def sortByKeys(s: Seq[Attribute], keys: Seq[String]): Seq[Attribute] = 
    keys.flatMap(key => s.filter(_.name == key)) 

    implicit def recordDecoder[A, R <: HList, LR <: HList, K <: HList, KL <: HList](
    implicit 
     gen: Generic.Aux[A, R], 
     lgen: LabelledGeneric.Aux[A, LR], 
     kk: Keys.Aux[LR, K], 
     m: Mapper.Aux[toName.type, K, KL], 
     toSeq: ToTraversable.Aux[KL, Seq, String], 
     genDecoder: HListDecoder[R]): RecordDecoder[A] = 
    new RecordDecoder[A] { 
     def decode(s: Seq[Attribute]) = { 
     val keys = kk.apply.map(toName).to[Seq] 
     val attrs = sortByKeys(s, keys) 
     genDecoder.decode(attrs).map(gen.from _) 
     } 
    } 

    def apply[A](s: Seq[Attribute])(implicit decoder: RecordDecoder[A]) = 
    decoder.decode(s) 

} 

測試用例:

implicit val stringDecoder = new FieldDecoder[String] { 
    override def decode(a: AttributeValue): Option[String] = Some(a.value) 
} 

implicit val intDecoder = new FieldDecoder[Int] { 
    override def decode(a: AttributeValue): Option[Int] = Some(a.value.toInt) 
} 

val attrs = Seq(
    Attribute("a", new AttributeValue("a")), 
    Attribute("c", new AttributeValue("2")), 
    Attribute("b", new AttributeValue("b"))) 

case class Test(b: String, a: String, c: Int) 

println(RecordDecoder[Test](attrs)) 

// Some(Test(b,a,2)) 
+0

我不知道這種方法是否適用於嵌套案例類? – Haspemulator

1

我被清除Github上爲靈感,並發現了一些在Frameless項目。看起來使用WitnessLabelledGeneric一起可以直接訪問字段名稱。我想出了以下版本的作品:

trait Decoder[A] { 
    def decode(s: Seq[Attribute]): Option[A] 
} 

object Decoder { 
    implicit val hnilDecoder = new Decoder[HNil] { 
    override def decode(s: Seq[Attribute]): Option[HNil] = { 
     Some(HNil) 
    } 
    } 
    implicit def keyedHconsDecoder[K <: Symbol, H, T <: HList](
     implicit key: Witness.Aux[K], 
     head: FieldDecoder[H], 
     tail: Decoder[T] 
): Decoder[FieldType[K, H] :: T] = 
    new Decoder[FieldType[K, H] :: T] { 
     def decode(s: Seq[Attribute]) = { 
     val fieldName = key.value.name 
     for { 
      head <- head.decode(s, fieldName) 
      tail <- tail.decode(s) 
     } yield labelled.field[K](head) :: tail 
     } 
    } 
    implicit def caseClassDecoder[A, R <: HList](
     implicit gen: LabelledGeneric.Aux[A, R], 
     reprDecoder: Lazy[Decoder[R]], 
     ct: ClassTag[A]): Decoder[A] = 
    new Decoder[A] { 
     override def decode(s: Seq[Attribute]): Option[A] = { 
     println(s"record decode case class ${ct.runtimeClass.getSimpleName}") 
     reprDecoder.value.decode(s).map(gen.from) 
     } 
    } 
    def apply[A](s: Seq[Attribute])(implicit decoder: Decoder[A], 
            ct: ClassTag[A]): Option[A] = { 
    println(s"start decoding for ${ct.runtimeClass}") 
    decoder.decode(s) 
    } 
} 

(省略FieldDecoder爲了簡潔)

人們也需要從Decoder[H :: T] HCons解碼器(keyedHconsDecoder)返回類型調整爲Decoder[[FieldType[K, H] :: T],因爲我們在這裏處理LabelledGeneric

+0

您能否發佈完整的源代碼,包括進口和定義? – eirirlar

+1

我已經把我現在的東西推到Github上,看看那裏:https://github.com/RomanIakovlev/dynamo-generic/blob/master/src/main/scala/net/iakovlev/dynamo/generic /named/Decoder.scala – Haspemulator