【问题标题】:How to extract label from Shapeless record for generic type class derivation如何从 Shapeless 记录中提取标签以进行泛型类型类派生
【发布时间】:2016-08-25 01:16:37
【问题描述】:

我正在编写一个泛型类型类(DynamoDB 的编解码器),使用 Shapeless 派生代码。我有一个不使用案例类的字段名称的版本,纯粹基于类字段的顺序与 DynamoDB 响应中的属性顺序相匹配的想法。它使用Generic 和通常的deriveHNilderiveHCons 方法,例如在此处描述:https://meta.plasm.us/posts/2015/11/08/type-classes-and-generic-derivation/

现在我想要一个使用字段名称来查找相关 DynamoDB 属性的版本。我目前的想法是主要重用以前(基于订单)版本的方法,另外让编译器通过LabelledGenericshapeless.ops.record.Keys 提供字段名称。但是,我坚持如何正确使用 Keys 功能。

想法如下:函数hconsDecoder应该一次做两件事:解构HList以在其头+尾上运行decode操作,并从上述头中提取标签。 LabelledGeneric 应该为HList 提供字段上的标签,以便hconsDecoder 中的H 类型参数将成为记录中的一个条目,包含相关信息。但是因为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,我不知道我是否走对了路。对于这个特定错误和我的总体方法,我将不胜感激。

【问题讨论】:

    标签: scala shapeless


    【解决方案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

    还需要将HCons解码器(keyedHconsDecoder)中的返回类型从Decoder[H :: T]调整为Decoder[[FieldType[K, H] :: T],因为我们在这里处理的是LabelledGeneric

    【讨论】:

    【解决方案2】:

    我之前遇到过同样的问题,但没有找到一种简单的方法来递归地将Keys HListGeneric HList 对齐。我希望有人会发布更好的解决方案。

    一个简单的解决方案是在递归处理之前将输入序列与键对齐。为了清楚起见,我已将 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))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-03-22
      • 2016-09-17
      • 2011-10-09
      • 2011-05-24
      • 2010-10-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多