2016-08-22 4 views
1

Shapelessでジェネリック型のクラス(DynamoDBのコーデック)を派生しています。クラスのフィールドの順序がDynamoDBレスポンスの属性の順序と一致するというアイデアのみに基づいて、ケースクラスのフィールド名を使用せずに動作するバージョンがあります。これは、Genericを使用し、通常はderiveHNil,deriveHConsアプローチを使用します(例:https://meta.plasm.us/posts/2015/11/08/type-classes-and-generic-derivation/)。ジェネリック型のクラス派生のためのShapelessレコードからのラベルの抽出方法

ここで、フィールド名を使用して関連するDynamoDB属性を検索するバージョンが必要です。私の現在の考え方は、ほとんどの場合、以前の(オーダーベースの)バージョンからのアプローチを再利用し、さらにコンパイラにLabelledGenericshapeless.ops.record.Keysを介してフィールド名を提供させることです。しかし、私は正しくKeys機能を使用する方法に固執しています。

考え方は以下の通りです:その頭+尻尾にdecode操作を実行するためにHListを分解し、また、前述の頭からラベルを抽出:機能hconsDecoderは一度に2つのことを行う必要があります。 LabelledGenericはのhconsDecoderのタイプパラメータが関連情報を含むレコードのエントリになるように、フィールド上のラベルにHListを提供する必要があります。しかし、KeysHListでしか動作しないため、H :: HNilを作成してKeysを実行します。

は、ここで私が持っているコードの一部です:could not find implicit value for parameter c: shapeless.ops.hlist.IsHCons[m.Out]:このコードを考えると

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 

     } 
    } 
} 

、コンパイルエラーは次のとおりです。私はimplicit not foundエラーのいくつかのバリエーションに直面して、同じものの異なるバージョンを試しました。結論としては、何らかの理由でH :: HNil構成ではKeysが機能しません。

これはShapelessでの私の最初の重大な試みであり、私が正しい方法をとっているかどうかはわかりません。私は、この特定のエラーと一般的な私のアプローチについての両方のフィードバックに感謝します。

答えて

1

以前は同じ問題に直面していましたが、KeysHListGenericHListと再帰的に並べるための簡単なアプローチは見つかりませんでした。誰かがより良い解決策を投稿することを願っています。

簡単な解決策は、再帰的処理の前に入力シーケンスをキーと整列させることです。私は、明確さのために、一般的なHList表現の処理からseq処理を分離しました。

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]からDecoder[[FieldType[K, H] :: T]にHConsデコーダ(keyedHconsDecoder)で戻り値の型を調整する必要がありますここではLabelledGenericを扱っています。

+0

輸入品や定義を含めた完全なソースを投稿できますか? – eirirlar

+1

私は現時点でGithubに行っていることを押して、見てきました:https://github.com/RomanIakovlev/dynamo-generic/blob/master/src/main/scala/net/iakovlev/dynamo/generic /named/Decoder.scala – Haspemulator

関連する問題