在我正在开发的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
}
}
实际上,以下将是一个类构造函数,该类构造函数将从查询字符串中读取的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)
最后一条语句不起作用,并返回错误:
<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)
但是假设这可行,我将要执行以下操作:
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)
我不知道要使第一个
.map
调用正常工作我需要做什么,而且我怀疑接下来的两个多态函数会遇到问题。 最佳答案
更新: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)))
}
还值得注意的是,如果您不介意过危险的生活,则可以跳过返回类型(在两种实现中):
object bind extends Poly1 {
implicit def rpb[T, K] = at[FieldType[K, RequestParamBuilder[T]]](b =>
field[K](b(requestParams))
)
}
但是总的来说,使用隐式方法和推断的返回类型是一个坏主意。
正如我在上面的评论中提到的,
Case
不是协变的,这意味着您的bind
仅在HList
的元素被静态键入为RequestParamBuilder
的情况下才有效(在这种情况下,您没有记录)。您可以使用
.values
从记录中获取值,然后可以在结果上进行映射,但是(如您所注意到的)这将意味着您丢失了键。如果要保留 key ,可以使用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))
}
现在
options.map(bind)
将按预期工作。我认为目前尚没有更好的方法来编写此代码,但我还没有密切关注最新的Shapeless开发。无论如何,这是相当清楚的,不是太冗长,它可以满足您的要求。
要回答您评论中的另一个问题:this previous question是一个起点,但是我不了解Shapeless中实现多态函数值的机制的真正好概述。对于博客文章来说,这是一个好主意。