映射无形记录

acj*_*jay 11 scala record shapeless

在我正在研究的Play应用程序中,我正在尝试改进我们的系统以处理标志,其中一些是用户通过链接导航我们的应用程序时的持久选项.我想使用Shapeless从选项的定义映射到它的值,并且还仅从标记为要传播的参数合成新的查询参数.我还希望能够利用Shapeless的Record功能来获得参数值的强类型解除引用.不幸的是,我不确定我是否在Shapeless中以有效的方式接近它.

以下是一段代码,被一些解释性注释所打断.

以下是我正在使用的基本数据类型:

import shapeless._
import poly._
import syntax.singleton._
import record._

type QueryParams = Map[String, Seq[String]]

trait RequestParam[T] {
  def value: T

  /** Convert value back to a query parameter representation */
  def toQueryParams: Seq[(String, String)]

  /** Mark this parameter for auto-propagation in new URLs */
  def propagate: Boolean

  protected def queryStringPresent(qs: String, allParams: QueryParams): Boolean = allParams.get(qs).nonEmpty
}

type RequestParamBuilder[T] = QueryParams => RequestParam[T]

def booleanRequestParam(paramName: String, willPropagate: Boolean): RequestParamBuilder[Boolean] = { params =>
  new RequestParam[Boolean] {
    def propagate: Boolean = willPropagate
    def value: Boolean = queryStringPresent(paramName, params)
    def toQueryParams: Seq[(String, String)] = Seq(paramName -> "true").filter(_ => value)
  }
}

def stringRequestParam(paramName: String, willPropagate: Boolean): RequestParamBuilder[Option[String]] = { params =>
  new RequestParam[Option[String]] {
    def propagate: Boolean = willPropagate
    def value: Option[String] = params.get(paramName).flatMap(_.headOption)
    def toQueryParams: Seq[(String, String)] = value.map(paramName -> _).toSeq
  }
}
Run Code Online (Sandbox Code Playgroud)

实际上,以下是一个类构造函数,它将查询字符串中的Map读取作为参数,但为了简单起见,我只是定义了一个val:

val requestParams = Map("no_ads" -> Seq("true"), "edition" -> Seq("us"))

// In reality, there are many more possible parameters, but this is simplified
val options = ('adsDebug ->> booleanRequestParam("ads_debug", true)) ::
  ('hideAds ->> booleanRequestParam("no_ads", true)) ::
  ('edition ->> stringRequestParam("edition", false)) ::
  HNil

object bind extends (RequestParamBuilder ~> RequestParam) {
  override def apply[T](f: RequestParamBuilder[T]): RequestParam[T] = f(requestParams)
}

// Create queryable option values record by binding the request parameters
val boundOptions = options.map(bind)
Run Code Online (Sandbox Code Playgroud)

最后一个语句不起作用,并返回错误:

<console>:79: error: could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper[bind.type,shapeless.::[RequestParamBuilder[Boolean] with shapeless.record.KeyTag[Symbol with shapeless.tag.Tagged[String("adsDebug")],RequestParamBuilder[Boolean]],shapeless.::[RequestParamBuilder[Boolean] with shapeless.record.KeyTag[Symbol with shapeless.tag.Tagged[String("hideAds")],RequestParamBuilder[Boolean]],shapeless.::[RequestParamBuilder[Option[String]] with shapeless.record.KeyTag[Symbol with shapeless.tag.Tagged[String("edition")],RequestParamBuilder[Option[String]]],shapeless.HNil]]]]
           val boundOptions = options.map(bind)
Run Code Online (Sandbox Code Playgroud)

但假设有效,我想做以下事情:

object propagateFilter extends (RequestParam ~> Const[Boolean]) {
  override def apply[T](r: RequestParam[T]): Boolean = r.propagate
}   

object unbind extends (RequestParam ~> Const[Seq[(String, String)]]) {
  override def apply[T](r: RequestParam[T]): Seq[(String, String)] = r.toQueryParams
}

// Reserialize a query string for options that should be propagated
val propagatedParams = boundOptions.values.filter(propagateFilter).map(unbind).toList 
// (followed by conventional collections methods)
Run Code Online (Sandbox Code Playgroud)

我不知道我需要做什么才能让第一次.map调用工作,我怀疑我将遇到下两个多态函数的问题.

Tra*_*own 7

更新:FieldPoly帮助器实际上并没有为你做那么多工作,你可以在没有它的情况下完成同样的事情(并且没有Witness隐含的):

import shapeless.labelled.{ FieldType, field }

object bind extends Poly1 {
  implicit def rpb[T, K]: Case.Aux[
    FieldType[K, RequestParamBuilder[T]],
    FieldType[K, RequestParam[T]]
  ] = at[FieldType[K, RequestParamBuilder[T]]](b => field[K](b(requestParams)))
}
Run Code Online (Sandbox Code Playgroud)

值得注意的是,如果你不介意生活危险,你可以跳过返回类型(在两个实现中):

object bind extends Poly1 {
  implicit def rpb[T, K] = at[FieldType[K, RequestParamBuilder[T]]](b =>
    field[K](b(requestParams))
  )
}
Run Code Online (Sandbox Code Playgroud)

但是通常使用带有推断返回类型的隐式方法是个坏主意.


正如我在上面的评论中提到的那样,Case不是协变的,这意味着bind只有当HList静态类型的元素被定义为RequestParamBuilder(在这种情况下你没有记录)时,你才能工作.

您可以使用.values从记录中获取值,然后您可以映射结果,但(如您所知)这将意味着您丢失了密钥.如果你想保留键,可以使用Shapeless FieldPoly,它可以帮助解决这种情况:

import shapeless.labelled.FieldPoly

object bind extends FieldPoly {
  implicit def rpb[T, K](implicit witness: Witness.Aux[K]): Case.Aux[
    FieldType[K, RequestParamBuilder[T]],
    FieldType[K, RequestParam[T]]
  ] = atField(witness)(_(requestParams))
}
Run Code Online (Sandbox Code Playgroud)

现在options.map(bind)将按预期工作.

我不认为现在有更好的方法来写这个,但我并没有非常密切地关注最近的无形发展.在任何情况下,这都是相当清楚的,不是太冗长,它可以做你想要的.

回答你的评论中的另一个问题:这个前一个问题是一个起点,但我不知道在Shapeless中实现多态函数值的机制非常好.博客文章是一个好主意.