使用 Shapeless 实现一个 (HList of Function, Input) => Tuple 类型类
Using Shapeless to implement an (HList of Function, Input) => Tuple typeclass
我有一些与
类似的现有代码
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]
}
为了简单起见,我将从处理 HList 而不是元组开始:
val someFields = Fields.Id :: Fields.Name :: Fields.OtherStuff :: HNil
我尝试使用我的 makeFieldExtractor
将 someFields
转换为 someFieldExtractors
。这就是我开始 运行 麻烦的地方。
val someFieldExtractors = someFields.map(makeFieldExtractor)
error: could not find implicit value for parameter mapper: 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 但没有成功使用它。
这是我的做法。
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)
我有一些与
类似的现有代码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]
}
为了简单起见,我将从处理 HList 而不是元组开始:
val someFields = Fields.Id :: Fields.Name :: Fields.OtherStuff :: HNil
我尝试使用我的 makeFieldExtractor
将 someFields
转换为 someFieldExtractors
。这就是我开始 运行 麻烦的地方。
val someFieldExtractors = someFields.map(makeFieldExtractor)
error: could not find implicit value for parameter mapper: 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 但没有成功使用它。
这是我的做法。
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)