Scala 高级类型和协变
Scala higher-kinded type and covariant
我正在尝试抽象某些库 API,它可以 return 任何类型 A
、Option[A]
或 Seq[A]
。
到目前为止,我有这样的东西:
type Const[T] = T
sealed abstract class Request[F[_], A]
case class GetOne(id: Int) extends Request[Const, Int]
case class GetMany() extends Request[Seq, String]
然后当我使用它时:
def get[F[_], A](request: Request[F, A]): F[A] = request match {
case GetOne(id) => client.getOne[F[A]](id)
case GetMany() => client.getMany[A]() // error: Seq[A] does not conform to F[A]
}
我明白为什么这行不通,因为 F[_]
不是 covariant
Seq[_]
的子类或类似的东西。但我不确定如何在仍然能够使用 Const[A]
的同时变通。我绝望了吗?请帮忙。
对于这种类型的多态性,您可以使用类型类概念
考虑
trait Client {
def getOne[X]: X
def getMany[X]: Seq[X]
}
type Const[T] = T
sealed abstract class Request[F[_], A]
case class GetOne(id: Int) extends Request[Const, Int]
case class GetMany() extends Request[Seq, String]
我们可以定义这样的类型类:
trait HandleRequest[R <: Request[F, A], F[_], A] {
def apply(request: R, client: Client): F[A]
}
并根据需要实例化它:
implicit object handleGetOne extends HandleRequest[GetOne, Const, Int] {
def apply(request: GetOne, client: Client): Int = client.getOne
}
implicit object handleGetMany extends HandleRequest[GetMany, Seq, String] {
def apply(request: GetMany, client: Client): Seq[String] = client.getMany
}
现在您可以按如下方式定义通用函数:
implicit class ClientOps(val client: Client) {
def get[R <: Request[F, A], F[_], A](request: R)(implicit handle: HandleRequest[R, F, A]): F[A] =
handle(request, client)
}
如果您想概括您的请求类型,例如:
case class GetOne[X](id: Int) extends Request[Const, X]
case class GetMany[X]() extends Request[Seq, X]
您可以将实例重新定义为:
implicit def handleGetOne[X] = new HandleRequest[GetOne[X], Const, X] {
def apply(request: GetOne[X], client: Client): X = client.getOne
}
implicit def handleGetMany[X] = new HandleRequest[GetMany[X], Seq, X] {
def apply(request: GetMany[X], client: Client): Seq[X] = client.getMany
}
几个月后回到这里,我意识到我可以使用 path-dependent type
来获得相同的结果,同时更简洁并且不需要类型类模式。
type Const[T] = T
sealed trait Request {
type F[_]
type A
type FA = F[A]
def query(client: Client): Future[FA]
}
case class GetOne(id: Int) extends Request {
type F[x] = Const[x]
type A = Int
def query(client: Client): Future[Int] = client.getOne(id)
}
case class GetMany(id: Int) extends Request {
type F[x] = Seq[x]
type A = String
def query(client: Client): Future[Seq[String]] = client.getMany(id)
}
然后我们可以调用它而不会出现类型参数爆炸:
def get[R <: Request](request: R): request.FA = request.query(client)
我正在尝试抽象某些库 API,它可以 return 任何类型 A
、Option[A]
或 Seq[A]
。
到目前为止,我有这样的东西:
type Const[T] = T
sealed abstract class Request[F[_], A]
case class GetOne(id: Int) extends Request[Const, Int]
case class GetMany() extends Request[Seq, String]
然后当我使用它时:
def get[F[_], A](request: Request[F, A]): F[A] = request match {
case GetOne(id) => client.getOne[F[A]](id)
case GetMany() => client.getMany[A]() // error: Seq[A] does not conform to F[A]
}
我明白为什么这行不通,因为 F[_]
不是 covariant
Seq[_]
的子类或类似的东西。但我不确定如何在仍然能够使用 Const[A]
的同时变通。我绝望了吗?请帮忙。
对于这种类型的多态性,您可以使用类型类概念
考虑
trait Client {
def getOne[X]: X
def getMany[X]: Seq[X]
}
type Const[T] = T
sealed abstract class Request[F[_], A]
case class GetOne(id: Int) extends Request[Const, Int]
case class GetMany() extends Request[Seq, String]
我们可以定义这样的类型类:
trait HandleRequest[R <: Request[F, A], F[_], A] {
def apply(request: R, client: Client): F[A]
}
并根据需要实例化它:
implicit object handleGetOne extends HandleRequest[GetOne, Const, Int] {
def apply(request: GetOne, client: Client): Int = client.getOne
}
implicit object handleGetMany extends HandleRequest[GetMany, Seq, String] {
def apply(request: GetMany, client: Client): Seq[String] = client.getMany
}
现在您可以按如下方式定义通用函数:
implicit class ClientOps(val client: Client) {
def get[R <: Request[F, A], F[_], A](request: R)(implicit handle: HandleRequest[R, F, A]): F[A] =
handle(request, client)
}
如果您想概括您的请求类型,例如:
case class GetOne[X](id: Int) extends Request[Const, X]
case class GetMany[X]() extends Request[Seq, X]
您可以将实例重新定义为:
implicit def handleGetOne[X] = new HandleRequest[GetOne[X], Const, X] {
def apply(request: GetOne[X], client: Client): X = client.getOne
}
implicit def handleGetMany[X] = new HandleRequest[GetMany[X], Seq, X] {
def apply(request: GetMany[X], client: Client): Seq[X] = client.getMany
}
几个月后回到这里,我意识到我可以使用 path-dependent type
来获得相同的结果,同时更简洁并且不需要类型类模式。
type Const[T] = T
sealed trait Request {
type F[_]
type A
type FA = F[A]
def query(client: Client): Future[FA]
}
case class GetOne(id: Int) extends Request {
type F[x] = Const[x]
type A = Int
def query(client: Client): Future[Int] = client.getOne(id)
}
case class GetMany(id: Int) extends Request {
type F[x] = Seq[x]
type A = String
def query(client: Client): Future[Seq[String]] = client.getMany(id)
}
然后我们可以调用它而不会出现类型参数爆炸:
def get[R <: Request](request: R): request.FA = request.query(client)