如何使用 shapeless 创建具有依赖类型的类型类实例

How to create an instances for typeclass with dependent type using shapeless

我正在尝试为具有依赖类型的类型 class 派生元组实例。我正在使用 shapeless 为元组元素创建 class 类型。我在匹配元组实例类型时遇到问题:

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

尝试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 推导即可。

您的代码中有几处错误。

首先,如果你return(k1, k2)那么k1k2应该是the[Identifiable[K1]].identify(id._1), the[Identifiable[K2]].identify(id._2) 相应地而不是你定义的相反。(错别字已修复。)

其次,您忘记了类型优化。您将 identifiableTuple 的 return 类型声明为 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 模式重写这个 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 == a1b == b1 类型为 a.Ka1.Kb.Kb1.K 不同(a1b1the[Identifiable[K1]]the[Identifiable[K2]])。所以你 return (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).