如何使用路径依赖类型 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 或都使用依赖类型)
- 用隐含证据证明依赖类型等于K
ev: 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)
我正在尝试在如下方法中使用依赖于类型的类型 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 或都使用依赖类型)
- 用隐含证据证明依赖类型等于K
ev: 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)