【问题标题】:Using Shapeless to implement an (HList of Function, Input) => Tuple typeclass使用 Shapeless 实现 (HList of Function, Input) => Tuple typeclass
【发布时间】:2023-03-31 14:41:01
【问题描述】:

我有一些现有的代码

trait Field[T]
object Fields {
  case object Id extends Field[Int]
  case object Name extends Field[String]
  // ... and so on
}

// basically just a Map[Field[_], Any]
class QueryResultData {
  def apply[T](field: Field[T]): T
}

def query(fields: Set[Field]): QueryMonad[QueryResultData]

例如,如果我想查询 Id 和 Name 数据,我需要执行以下操作:

val idsAndNames = for {
  results <- query(Set(Fields.Id, Fields.Name))
} yield {
  val id = results(Fields.Id)
  val name = results(Fields.Name)
  (id, name)
}

必须手动提取每个字段的结果很乏味,尤其是当查询包含更多字段时。我想做的是:

val idsAndNames: QueryMonad[(Int, String)] = query(Fields.Id -> Fields.Name)

并让某种类型类处理val id = ... 部分并为我重建元组,例如

def query[Fields <: HList, Tuple](fields: Fields)
  (implicit extractor: Extractor[Fields, T])
  : QueryMonad[T]

如何实现Extractor 类型类,这样我就不必手动提取结果?


我的尝试

我认为这是 Shapeless 的工作,因为 query 方法适用于任意数量的字段,并且有望返回给我一个适当的元组。

我定义了一个FieldExtractor 类型:

class FieldExtractor[T](field: Field[T]) {
  def apply(results: QueryResultData): T = results(field)
}

以及 Field 到 FieldExtractor 的多态函数:

object makeFieldExtractor extends (Field ~> FieldExtractor) {
  def apply[T](field: Field[T]) = new FieldExtractor[T]
}

为了简单起见,我将从处理 HLists 而不是 Tuples 开始:

val someFields = Fields.Id :: Fields.Name :: Fields.OtherStuff :: HNil

我尝试使用我的makeFieldExtractorsomeFields 转换为someFieldExtractors。这就是我开始遇到麻烦的地方。

val someFieldExtractors = someFields.map(makeFieldExtractor)

错误:找不到参数映射器的隐式值:shapeless.ops.hlist.Mapper[MakeFieldExtractor.type,shapeless.::[Fields.Id.type,shapeless.::[Fields.Name.type,shapeless. ::[Fields.OtherStuff.type,shapeless.HNil]]]]

问题似乎是它看到了像Fields.Id.type 这样的类型,而它可能应该看到Field[Int]。如果我明确指定someFields 的字段类型,则映射有效,但我不希望客户端代码必须这样做。编译器应该为我这样做。假设我不能将Id/Name 定义更改为val 而不是case object

我找到了https://github.com/milessabin/shapeless/blob/master/examples/src/main/scala/shapeless/examples/klist.scala,但没有成功使用它。

【问题讨论】:

    标签: scala shapeless


    【解决方案1】:

    这就是我的做法。

    import shapeless.{::, HList, HNil}
    import Field._
    
    trait Field[A]
    object Field {
      case object IntField extends Field[Int]
      case object StringField extends Field[String]
      // Here is a little trick to proof that for any T that
      // happened to be a subclass of Field[A] the Out is A
      implicit def fieldExtractor[T, A]
      (implicit ev: T <:< Field[A]): Extractor.Aux[T, A] =
        new Extractor[T] {
          override type Out = A
        }
    }
    
    // The extractor for A
    trait Extractor[A] {
      type Out // Produces result of type Out
    }
    
    object Extractor {
      // The Aux pattern http://gigiigig.github.io/posts/2015/09/13/aux-pattern.html
      type Aux[A, Out0] = Extractor[A] {
        type Out = Out0
      }
    
      // Proof that Out for HNil is HNil
      implicit val hnilExtractor: Aux[HNil, HNil] = 
        new Extractor[HNil] {
          override type Out = HNil
        }
    
      // Proof that Out for T :: H is hlist of extractor result for H and T
      implicit def hconsExtractor[H, HO, T <: HList, TO <: HList]
      (implicit H: Aux[H, HO], T: Aux[T, TO]): Aux[H :: T, HO :: TO] =
        new Extractor[H :: T] {
          override type Out = HO :: TO
        }
    }
    
    type QueryMonad[A] = A
    
    // Use dependent type Out as a result
    def query[Fields](fields: Fields)(implicit extractor: Extractor[Fields]): QueryMonad[extractor.Out] = ???
    
    
    val result = query(IntField :: StringField :: HNil)
    

    【讨论】:

    • 谢谢!归纳构建Extractor 实例的方法效果很好
    • @Dylan 你有在query 方法中替换??? 的简单代码吗?我想不出任何可以编译的东西,我想试试你的例子。
    猜你喜欢
    • 1970-01-01
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多