使用Shapeless实现(函数的HList,输入)=>元组类型

时间:2016-10-05 21:17:53

标签: scala shapeless

我已经有了一些现有的代码

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 to FieldExtractor的多态函数:

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

为了简单起见,我首先要与HLists而不是元组打交道:

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,但没有设法成功使用它。

1 个答案:

答案 0 :(得分:2)

以下是我将如何做到这一点。

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)