Scala 当typeclass中有typeclass时,使用typeclass语法的正确方法是什么?

Scala 当typeclass中有typeclass时,使用typeclass语法的正确方法是什么?,scala,inner-classes,typeclass,implicit,Scala,Inner Classes,Typeclass,Implicit,我有一种情况,我使用一个typeclass-in-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 Ref

我有一种情况,我使用一个typeclass-in-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创建一个“syntax”对象,因此可以直接从实现typeclass的值调用这些方法。我的第一次尝试如下所示,类型检查正常:

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)
  }
}
然而,我在使用这个(例如)的时候遇到了一些奇怪的错误,我想知道我写这个的方式是否是罪魁祸首。
IsArrayOps1
类与refTc-typeclass之间存在有效的依赖关系,如果
isArTc
typeclass是方法的参数,而不是
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
typeclass作为
隐式类的属性,而不是每个方法,似乎可以减少样板文件并简化typeclass依赖关系,但我没有看到它被使用过,我想知道它是否出于其他原因而不受欢迎?
refTc:isArTc.refTc[R]
是属于另一个typeclass的typeclass的正确语法,还是应该更像
refTc:IsArray #refTc[R]

嵌套类型类使用得更少,但原则上可以使用它们

嵌套类型类的另一个示例是

这不是类型检查,可能需要使
Aux
模式工作

否,
Aux
模式将不起作用
Aux
模式有助于处理类型参数/类型成员中的依赖项,但不能处理前缀中的依赖项,如
IsArrayOps2
。这种依赖性在Scala 2中是无法表达的

实际上,将隐式参数拆分为类级参数和方法级参数(如
IsArrayOps1
)是解决这种依赖性的正确方法

它是否应该更像
refTc:IsArray#refTc[R]

不,类型投影不能很好地使用隐式分辨率

您可以检查类型投影的语法是否不起作用

我在使用这个时遇到了一些奇怪的错误(例如使用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]
}
我将implicits
Numeric.IntIsIntegral
Numeric.doubleisfrancial
导入到相应的作用域,以避免像中那样的歧义

顺便说一下,您可以用一个类型类来表达相同的逻辑,该类添加了更多的类型参数和条件隐式(
listIsArray

或者只是

abstract class IsArray[A, T: Numeric, Col[_], Out] {
  def getRef(self: A, ref: Col[T]): Out
}
在我们的例子中,
Col
可以是
Id
List

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)
  }
}
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
}