In un'applicazione Play su cui sto lavorando, sto cercando di migliorare il nostro sistema per l'elaborazione dei flag, alcuni dei quali sono pensati per essere opzioni persistenti mentre un utente naviga la nostra app tramite link. Mi piacerebbe usare Shapeless per mappare da una definizione dell'opzione al suo valore, e anche per sintetizzare un nuovo parametro di query solo da quelli contrassegnati per essere propagati. Voglio anche essere in grado di sfruttare la funzionalità Record
di Shapeless per ottenere la dereferenziazione fortemente tipizzata dei valori dei parametri. Sfortunatamente, non sono sicuro se mi sto avvicinando a questo in modo valido in Shapeless.Mapping su record senza forma
Quanto segue è un blocco di codice, interrotto da alcuni commenti esplicativi.
Ecco i tipi di dati di base con cui sto lavorando:
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
}
}
In realtà, il seguente sarebbe un costruttore di classe che prende questo Map leggere dalla stringa di query come parametro, ma per semplicità , sto solo definendo un 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)
Quest'ultima affermazione non funziona, e restituisce l'errore:
<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)
.210
Ma supponendo che ha funzionato, vorrei fare quanto segue:
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)
non so che cosa devo fare per ottenere quel primo .map
chiamata a lavorare, e ho il sospetto che sarò in esecuzione in problemi con le prossime due funzioni polimorfiche.
'options.values.map (bind)' dovrebbe funzionare ma non lo fa. Definire 'bind' in termini di' Poly1' risolve direttamente le cose - non sono sicuro del perché al momento, ma posso dare un'occhiata dopo. –
Non dovrei essere in grado di mappare il record stesso? Voglio comunque poter accedere alle opzioni associate tramite i tasti. – acjay
'Case' non è covariante, e gli elementi del record' HList' sono sottotipi di 'RequestParam'. È anche possibile riscrivere 'bind' per accettare tali sottotipi se si desidera conservare le chiavi. –