我有一种情况,我使用一个typeclass-within-a-typeclass来重载原始typeclass的方法。以下示例:
abstract class IsArray[A, T: Numeric] {
def getSingleElem(self: A, idx: Int): T
def getRef[R](self: A, ref: R)(implicit refTc: RefTC[R]): refTc.Out = refTc.getRef(self, ref)
trait RefTC[R] {
type Out
def getRef(self: A, ref: R): Out
}
object RefTC {
implicit val numsTcForSingleInt = new RefTC[Int] {
type Out = T
def getRef(self: A, ref: Int): Out = getSingleElem(self, ref)
}
implicit val numsTcForListInt = new RefTC[List[Int]] {
type Out = List[T]
def getRef(self: A, ref: List[Int]): Out = ref.map(getSingleElem(self, _))
}
}
}
这一切都很好。我遇到的困难是为typeclass创建一个"语法"对象,因此可以直接从实现typeclass的值中调用方法。我的第一次尝试看起来是这样的,类型检查OK:
object IsArraySyntax {
implicit class IsArrayOps1[A, T: Numeric](self: A)(implicit isArTc: IsArray[A, T]) {
def getSingleElem(idx: Int): T = isArTc.getSingleElem(self, idx)
def getRef[R](ref: R)(implicit refTc: isArTc.RefTC[R]): refTc.Out = refTc.getRef(self, ref)
}
}
然而,我在使用它时遇到了一些奇怪的错误(例如,使用ScalaTest时出现java.lang.NoSuchFieldError(,我想知道我写这篇文章的方式是否应该受到谴责。isArTc
类型类和refTc类型类之间实际上存在依赖关系,如果isArTc
类型类是方法的参数而不是IsArrayOps1
类的参数,则该依赖关系将变为显式依赖关系,如:
object IsArraySyntax {
implicit class IsArrayOps2[A, T: Numeric](self: A) {
def getSingleElem(idx: Int)(implicit isArTc: IsArray[A, T]): T = isArTc.getSingleElem(self, idx)
def getRef[R](ref: R)(implicit isArTc: IsArray[A, T], refTc: isArTc.RefTC[R]): refTc.Out = refTc.getRef(self, ref)
}
}
这不是类型检查,可能需要使Aux
模式工作。
但我更想知道这里的最佳实践是什么?将isArTc
类型类作为implicit class
的属性,而不是每个方法,似乎可以减少样板文件并简化类型类依赖关系,但我还没有看到它被使用过,我想知道这是否是出于其他原因而不可取的?refTc: isArTc.RefTC[R]
是属于另一个类型类的类型类的正确语法吗?还是应该更像refTc: IsArray#RefTC[R]
?
嵌套类型类的另一个例子是如何避免在Scala 中调用asInstanceOf
这不进行类型检查,可能需要使
Aux
模式工作。
不,Aux
模式没有帮助。Aux
模式有助于处理类型参数/类型成员中的依赖关系,但不能像IsArrayOps2
那样处理前缀中的依赖性。这种依赖性在Scala 2中是无法表达的。
实际上,将隐式参数分为类级和方法级参数(如IsArrayOps1
(是解决这种依赖关系的正确方法。
它应该更像
refTc: IsArray#RefTC[R]
吗?
不,类型投影在隐式分辨率中效果不佳
https://typelevel.org/blog/2015/07/23/type-projection.html
您可以检查使用类型投影时语法是否不起作用。
我在使用它时遇到了一些奇怪的错误(例如,使用ScalaTest时出现java.lang.NoSuchFieldError(,我想知道我写这篇文章的方式是否是罪魁祸首。
您的类型类和语法#1似乎可以使用
case class MyClass(is: List[Int])
object MyClass {
implicit val mcIsIntArray: IsArray[MyClass, Int] = new IsArray[MyClass, Int] {
override def getSingleElem(self: MyClass, idx: Int): Int = self.is(idx)
}
implicit val mcIsDoubleArray: IsArray[MyClass, Double] = new IsArray[MyClass, Double] {
override def getSingleElem(self: MyClass, idx: Int): Double = self.is(idx)
}
}
val ia = implicitly[IsArray[MyClass, Int]]
implicitly[ia.RefTC[Int] { type Out = Int}]
implicitly[ia.RefTC[List[Int]] { type Out = List[Int]}]
val ia1 = implicitly[IsArray[MyClass, Double]]
implicitly[ia1.RefTC[Int] { type Out = Double}]
implicitly[ia1.RefTC[List[Int]] { type Out = List[Double]}]
implicitly[IsArray[MyClass, Int]].getSingleElem(MyClass(List(1, 2, 3)), 1) // 2
implicitly[IsArray[MyClass, Int]].getRef(MyClass(List(1, 2, 3)), 1) // 2
implicitly[IsArray[MyClass, Int]].getRef(MyClass(List(1, 2, 3)), List(1, 0)) // List(2, 1)
implicitly[IsArray[MyClass, Double]].getSingleElem(MyClass(List(1, 2, 3)), 1) // 2.0
implicitly[IsArray[MyClass, Double]].getRef(MyClass(List(1, 2, 3)), 1) // 2.0
implicitly[IsArray[MyClass, Double]].getRef(MyClass(List(1, 2, 3)), List(1, 0)) // List(2.0, 1.0)
import IsArraySyntax._
{
import Numeric.IntIsIntegral // to avoid ambiguity
MyClass(List(1, 2, 3)).getSingleElem(1): Int
MyClass(List(1, 2, 3)).getRef(1): Int
MyClass(List(1, 2, 3)).getRef(List(1, 0)): List[Int]
}
{
import Numeric.DoubleIsFractional // to avoid ambiguity
MyClass(List(1, 2, 3)).getSingleElem(1): Double
MyClass(List(1, 2, 3)).getRef(1): Double
MyClass(List(1, 2, 3)).getRef(List(1, 0)): List[Double]
}
我将隐式Numeric.IntIsIntegral
、Numeric.DoubleIsFractional
导入到相应的作用域,以避免像隐式视图中那样的歧义不起作用——这是我的隐式定义造成的吗?
顺便说一句,你可以用一个类型类来表达同样的逻辑,添加更多的类型参数和条件隐式(listIsArray
(
abstract class IsArray[A, T: Numeric, R, Out] {
def getRef(self: A, ref: R): Out
}
trait LowPriorityIsArray {
implicit def listIsArray[A, T: Numeric, R, Out](implicit
singleIsArray: IsArray[A, T, R, Out]
): IsArray[A, T, List[R], List[Out]] = new IsArray[A, T, List[R], List[Out]] {
override def getRef(self: A, ref: List[R]): List[Out] =
ref.map(singleIsArray.getRef(self, _))
}
}
object IsArray extends LowPriorityIsArray {
implicit val mcIsIntArray: IsArray[MyClass, Int, Int, Int] = new IsArray[MyClass, Int, Int, Int] {
override def getRef(self: MyClass, idx: Int): Int = self.is(idx)
}
implicit val mcIsDoubleArray: IsArray[MyClass, Double, Int, Double] = new IsArray[MyClass, Double, Int, Double] {
override def getRef(self: MyClass, idx: Int): Double = self.is(idx)
}
}
object IsArraySyntax {
implicit class IsArrayOps3[A, T: Numeric, R, Out](self: A) {
def getSingleElem(idx: Int)(implicit
isAr: IsArray[A, T, R, Out],
ev: Int <:< R
): Out = isAr.getRef(self, idx)
def getRef(ref: R)(implicit isAr: IsArray[A, T, R, Out]): Out =
isAr.getRef(self, ref)
}
}
case class MyClass(is: List[Int])
import IsArraySyntax._
{
import Numeric.IntIsIntegral
MyClass(List(1, 2, 3)).getSingleElem(1): Int
MyClass(List(1, 2, 3)).getRef(1): Int
MyClass(List(1, 2, 3)).getRef(List(1, 0)): List[Int]
}
{
import Numeric.DoubleIsFractional
MyClass(List(1, 2, 3)).getSingleElem(1): Double
MyClass(List(1, 2, 3)).getRef(1): Double
MyClass(List(1, 2, 3)).getRef(List(1, 0)): List[Double]
}
注意语法
object IsArraySyntax {
implicit class IsArrayOps4[A, T: Numeric, R, Out](self: A)(implicit
isAr: IsArray[A, T, R, Out]
) {
def getSingleElem(idx: Int)(implicit ev: Int <:< R): Out =
isAr.getRef(self, idx)
def getRef(ref: R): Out = isAr.getRef(self, ref)
}
}
不会起作用。
或者,您可以将Out
设置为类型成员,而不是类型参数。
好吧,我刚刚注意到我在类型类中实际上没有使用T
,所以这可能不是你想要的。你可以尝试一个更高级类型的
abstract class IsArray[A, T: Numeric, Col[_], R <: Col[T], Out] {
def getRef(self: A, ref: R): Out
}
或者只是
abstract class IsArray[A, T: Numeric, Col[_], Out] {
def getRef(self: A, ref: Col[T]): Out
}
在我们的实例中,Col
可以是Id
或List
。