Polymorphic functions with constant return type in Shapeless

北慕城南 提交于 2019-12-22 09:24:23

问题


Long story short, I'm trying to figure out how to define a function from a generic input to a single-typed output.

The background: This is a continuation of Mapping over Shapeless record. After Travis's excellent answer, I now have the following:

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 reality, the following would be a class constructor that takes this Map read from the query string as a parameter, but for simplicity's sake, I'm just defining a 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 FieldPoly {
  implicit def rpb[T, K](implicit witness: Witness.Aux[K]): Case.Aux[
      FieldType[K, RequestParamBuilder[T]],
      FieldType[K, RequestParam[T]]
    ] = atField(witness)(_(requestParams))
}

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

This lets me do:

boundOptions.get('hideAds).value // -> true

The problem: Now I want to be able to reserialize options that have propagate = true. So basically, I need to filter my HList on the propagate field of every member, which should always return a Boolean, and then have each parameter reserialize itself to a Seq[(String, String)]. I've tried the following:

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)

, but it doesn't like my functions. I got the following error:

<console>:31: error: type mismatch;
 found   : Boolean
 required: shapeless.Const[T]
    (which expands to)  AnyRef{type λ[T] = T}
             override def apply[T](r: RequestParam[T]) = r.propagate

I believe I'm taking the wrong approach to a function that's supposed to have a polymorphic input but monomorphic output.

Other failed attempts:

object propagateFilter extends Poly1 {
  implicit def default[T](implicit st: Case.Aux[RequestParam[T], Boolean]) = at[RequestParam[T]](_.propagate)
}

and

def propagate[T](x: RequestParam[T]): Boolean = x.propagate

and

object propagateFilter extends Poly1 {
  implicit def default = at[RequestParam[_]](_.propagate)
}

and

object propagateFilter extends FieldPoly {
  implicit def rpb[T, K](implicit witness: Witness.Aux[K]): Case.Aux[
    FieldType[K, RequestParam[T]],
    Boolean
    ] = atField(witness)(_.propagate)
}

None of which work, probably due to my own misunderstanding of what's going on.

来源:https://stackoverflow.com/questions/26388632/polymorphic-functions-with-constant-return-type-in-shapeless

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!