如何使用路径依赖类型 class

How to use a path dependent type class

我正在尝试在如下方法中使用依赖于类型的类型 class:

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

object Identifiable {
  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)
    }
  }

但是,当我尝试像下面那样使用它时,隐式参数没有解析

def a[K: Identifiable](key:K) = { 
case k => 
 val keyString: String = the[Identifiable.Aux[K, String]].identify(key) 
case (k1,k2) => 
 val keyString: (String,String) = the[Identifiable.Aux[K, (String, String)]].identify(key) }

你要求在类型约束中使用依赖于路径的类型进行隐式调用,但随后你使用固定类型调用隐式。

为此,编译器必须能够证明 Identifiable 中的依赖类型等于 K,这在当前代码中无法做到。

你可以试试:

  • 在两个地方使用相同的约定(都使用 Aux 或都使用依赖类型)
  • 用隐含证据证明依赖类型等于Kev: identifiable.K =:= K

模式 (k1,k2) 应该在不受约束的模式 k 之前,否则所有内容都会转到 k 而不会转到 (k1,k2).

模式匹配主要在运行时工作,类型classes/implicits主要在编译时工作。自从您开始使用 classes 类型(以及带有类型成员的 classes 类型)以来,您似乎想要对类型进行一些计算,即在编译时进行一些计算。

运行时的逻辑分支对应于模式匹配。编译时的逻辑分支对应于一个类型 class 和几个隐式定义类型 class.

的实例

尝试使a()成为一种类型class

trait A[K] {
  def apply(key: K): Unit
}

object A {
  implicit def tupleA[K](implicit identifiable: Identifiable.Aux[K, (String, String)]): A[K] = new A[K] {
    override def apply(key: K): Unit = key match {
      case (k1, k2) =>
        val keyString: (String, String) = identifiable.identify(key)
        ()
    }
  }

  implicit def defaultA[K](implicit identifiable: Identifiable.Aux[K, String]): A[K] = new A[K] {
    override def apply(key: K): Unit = {
      val keyString: String = identifiable.identify(key)
      ()
    }
  }
}

def a[K](key: K)(implicit aInstance: A[K]) = aInstance(key)

您应该写一些示例,说明您打算如何应用 a()。您希望在什么输入上得到什么输出。什么该编译,什么不该编译?

通常类型参数(如 M 对应 Identifiable[M])类似于输入,类型成员(如 K 对应 Identifiable[M] { type K })类似于类型级计算的输出.因此,对于 Identifiable,看起来类型 K 将根据 M 的类型来选择。这是正确的吗?请考虑您的逻辑(什么是输入,什么输出)。在 a() 中,您似乎开始根据 K 的类型做某事。

但您为什么期望它们匹配?在 case k => 分支中,有一个类型为 Identifiable[K] 的隐含类型,但没有理由期望它是您尝试使用 the 请求的 Identifiable[K] { type K = String }。或者另一个分支中的 Identifiable[K] { type K = (String, String) } 。 (请注意,K 有两种不同的含义,这可能有点令人困惑。)

[From a comment] case class K1(value: String); case class K2(value: String) I expect: a(K1) => String and a((K1,K2)) => (String, String)

接近你想要的最合理的选择似乎很简单

def a[A](key:A)(implicit ev: Identifiable[A]): ev.K = ev.identify(key)