Как создать экземпляры для класса типов с зависимым типом, используя бесформенный - PullRequest
2 голосов
/ 05 апреля 2020

Я пытаюсь получить экземпляр кортежа для класса типов с зависимым типом. Я использую бесформенную форму для создания класса вызова для элементов кортежа. У меня возникают проблемы при сопоставлении типов экземпляров кортежа:

import shapeless.the
import simulacrum.typeclass

@typeclass trait Identifiable[M] {
  type K
  def identify(id: M): K
}

object Identifiable{
  implicit def identifiableTuple[K1: Identifiable, K2: Identifiable]: Identifiable[(K1,K2)] = new Identifiable[(K1,K2)]{
     val b = the[Identifiable[K2]]
    val a = the[Identifiable[K1]]
    type K = (a.K, b.K)   
    override def identify(id: (K1, K2)): K = {
          val k1 = the[Identifiable[K1]].identify(id._1)
          val k2 = the[Identifiable[K2]].identify(id._2)
          (k1,k2)
        }
  }

Я получаю эту ошибку:

type mismatch;
 found   : k1.type (with underlying type ai.fugo.cms.service.common.domain.Identifiable[K2]#K)
 required: this.a.K

type mismatch;
 found   : k2.type (with underlying type ai.fugo.cms.service.common.domain.Identifiable[K1]#K)
 required: this.b.K

Ответы [ 2 ]

2 голосов
/ 05 апреля 2020

Попробуйте Aux шаблон

trait Identifiable[M] {
  type K
  def identify(id: M): K
}

object Identifiable {
  type Aux[M, K0] = Identifiable[M] { type K = K0 }

  implicit def identifiableTuple[M1, K1, M2, K2](
    implicit
    identifiable1: Identifiable.Aux[M1, K1],
    identifiable2: Identifiable.Aux[M2, K2]
  ): Identifiable.Aux[(M1, M2), (K1, K1)] = new Identifiable[(M1, M2)] {
    type K = (K1, K2)
    def identify(id: (M1, M2)): (K1, K2) =
      identifiable1.identify(id._1) -> identifiable2.identify(id._2)
  }
}

Шаблон Aux был изобретен, потому что

  • человеку легче читать его
  • Я думаю (?) у компилятора были проблемы с производными классами типов для зависимых от пути типов ... но не для их псевдонимов

Так что просто используйте Aux для получения вещей.

1 голос
/ 05 апреля 2020

В вашем коде есть несколько ошибок.

Во-первых, если вы вернете (k1, k2), тогда k1, k2 должно быть the[Identifiable[K1]].identify(id._1), the[Identifiable[K2]].identify(id._2) соответственно и не наоборот, как вы их определили.

Во-вторых, вы забыли уточнение типа. Вы объявляете тип возврата identifiableTuple как Identifiable[(K1,K2)] вместо правильного Identifiable[(K1,K2)] { type K = (a.K, b.K)} (он же Identifiable.Aux[(K1,K2), (a.K, b.K)]). Если вы сохраняете Identifiable[(K1,K2)], вы на самом деле вытесняете правую сторону

new Identifiable[(K1,K2)]{
  ...
  type K = (a.K, b.K)   
  ...
}

и информацию, которая для этого неявного экземпляра type K = (a.K, b.K) будет потеряна.

Поскольку вам нужно восстановить уточнение типа, вы можете не пишите identifiableTuple с контекстными границами, вы должны написать его неявным блоком

implicit def identifiableTuple[K1, K2](implicit
  a: Identifiable[K1],
  b: Identifiable[K2]
): Identifiable[(K1, K2)] {type K = (a.K, b.K)} = new Identifiable[(K1, K2)] {
  type K = (a.K, b.K)
  override def identify(id: (K1, K2)): K = {
    val k1 = a.identify(id._1)
    val k2 = b.identify(id._2)
    (k1, k2)
  }
}

Вы можете проверить свой код во время компиляции

implicit val int: Identifiable[Int] { type K = Double } = null
implicit val str: Identifiable[String] { type K = Char } = null
implicitly[Identifiable[(Int, String)] { type K = (Double, Char)}]

Вы можете переписать это с помощью Aux pattern type Aux[M, K0] = Identifiable[M] { type K = K0 }

implicit def identifiableTuple[K1, K2](implicit
  a: Identifiable[K1],
  b: Identifiable[K2]
): Identifiable.Aux[(K1, K2), (a.K, b.K)] = new Identifiable[(K1, K2)] {
  type K = (a.K, b.K)
  override def identify(id: (K1, K2)): K = {
    val k1 = a.identify(id._1)
    val k2 = b.identify(id._2)
    (k1, k2)
  }
} // (*)

и

implicit val int: Identifiable.Aux[Int, Double] = null
implicit val str: Identifiable.Aux[String, Char] = null
implicitly[Identifiable.Aux[(Int, String), (Double, Char)]]

Это похоже на ответ @ MateuszKubuszok

implicit def identifiableTuple[M1, M2, K1, K2](implicit
  a: Identifiable.Aux[M1, K1],
  b: Identifiable.Aux[M2, K2]
): Identifiable.Aux[(M1, M2), (K1, K2)] = new Identifiable[(M1, M2)] {
  type K = (K1, K2)
  override def identify(id: (M1, M2)): K = {
    val k1 = a.identify(id._1)
    val k2 = b.identify(id._2)
    (k1, k2)
  }
} // (**)

, хотя последний нуждается в дополнительном определении двух типов параметров.

И в-третьих, вы не можете написать (*) с implicitly или даже the внутри, как

implicit def identifiableTuple[K1, K2](implicit
  a: Identifiable[K1],
  b: Identifiable[K2]
): Identifiable.Aux[(K1, K2), (a.K, b.K)] = new Identifiable[(K1, K2)] {
  type K = (a.K, b.K)
  override def identify(id: (K1, K2)): K = {
    val k1 = the[Identifiable[K1]].identify(id._1)
    val k2 = the[Identifiable[K2]].identify(id._2)
    (k1, k2)
  }
}

Дело в том, что путь зависит от типы определены в Scala, так что даже когда a == a1, b == b1 типы a.K и a1.K, b.K и b1.K различны (a1, b1 равны the[Identifiable[K1]], the[Identifiable[K2]]). Таким образом, вы возвращаете (k1, k2) неправильного типа (a1.K,b1.K).

Но если вы напишите это в (**) стиле

implicit def identifiableTuple[M1, M2, K1, K2](implicit
  a: Identifiable.Aux[M1, K1],
  b: Identifiable.Aux[M2, K2]
): Identifiable.Aux[(M1, M2), (K1, K2)] = new Identifiable[(M1, M2)] {
  type K = (K1, K2)
  override def identify(id: (M1, M2)): K = {
    val k1 = the[Identifiable[M1]].identify(id._1)
    val k2 = the[Identifiable[M2]].identify(id._2)
    (k1, k2)
  }
}

, тогда все будет хорошо (с the, но не с implicitly), потому что компилятор делает вывод, что (k1,k2) имеет тип (K1,K2).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...