【发布时间】: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
我尝试使用我的makeFieldExtractor 将someFields 转换为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,但没有成功使用它。
【问题讨论】: