Высокодородный тип Scala и ковариантный

Я пытаюсь абстрагироваться от некоторого библиотечного API, который может возвращать любой из типов 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]. Я безнадежен? Пожалуйста помоги.


person Daniel Shin    schedule 24.10.2015    source источник
comment
Откуда взялся тип FetchRequest, какова его сигнатура?   -  person isaias-b    schedule 24.10.2015
comment
@isi Ой. Это была опечатка. Это Request.   -  person Daniel Shin    schedule 24.10.2015
comment
Итак, client.getMany[A]() возвращает Seq[A] и вызов вашей библиотеки, верно?   -  person isaias-b    schedule 24.10.2015
comment
@isi Верно.   -  person Daniel Shin    schedule 24.10.2015
comment
Я сам все еще новичок в высокодородных типах, но я думаю, что ваш подход нацелен на слишком распространенное семейство. Разрешение вашей функции get получать какие-либо F[_] заставляет меня задаться вопросом, что бы означал ваш вызов, если бы F был List или чем-то менее очевидным, например Future. Я думаю, что в данном случае использование более высокого родственного типа слишком абстрактно. У функции не было бы шанса гарантировать, что заданы только допустимые типы F, что приведет к MatchError и прерыванию во время выполнения или, если доступно, предоставит допустимое значение по умолчанию в этом случае. Но я подозреваю, что это будет полезно по умолчанию;)   -  person isaias-b    schedule 24.10.2015


Ответы (2)


Для такого типа полиморфизма вы можете использовать концепцию класса типов
Учитывая, что

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
} 
person Odomontois    schedule 24.10.2015
comment
Под таким типом полиморфизма, я думаю, подразумевается: при абстрагировании над конечным набором N типов. Использование этого определения позволяет сказать: Найдите абстракцию A, говорящую Request, которая обертывает ваши N типы, например. с использованием наследования и / или более высокодородных типов. Затем ограничьте операции, чтобы использовать вашу абстракцию A или любой ее ограниченный тип. С помощью этого определения можно дать что-то вроде рецепта для повторного использования ответа или вопроса OP в общем контексте. И ваш ответ дает уже два решения AFAIK. - person isaias-b; 24.10.2015
comment
Браво! Это так умно. Оно работает. Большое спасибо! Я восхищаюсь твоими навыками scala:] - person Daniel Shin; 24.10.2015

Возвращаясь к этому через несколько месяцев, я понял, что мог бы использовать 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)
person Daniel Shin    schedule 08.12.2015