Nested type classes are used rarer but in principle they can be used.
Another example of nested type class is How to avoid calling asInstanceOf in Scala
This does not type check and presumably requires the Aux pattern to be made to work.
No, Aux pattern will not help. Aux pattern helps with dependencies in type parameters/type members but can't help with depedencies in prefixes like in IsArrayOps2. Such kind of dependency just can't be expressed in Scala 2.
Actually, splitting implicit parameters into class-level and method-level ones (like in IsArrayOps1) is correct way to fight with such kind of dependency.
should it be something more like refTc: IsArray#RefTC[R]?
No, type projections don't play well with implicit resolution
https://typelevel.org/blog/2015/07/23/type-projection.html
You can check that with type projections your syntax will not work.
I've been getting some strange errors whilst using this (eg java.lang.NoSuchFieldError when using ScalaTest) and I'm wondering if the way I'm writing this is to blame.
Your type classes and syntax #1 seem to work
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]
}
I imported implicits Numeric.IntIsIntegral, Numeric.DoubleIsFractional to corresponding scopes to avoid ambiguity like in Implicit view not working - is my implicit def to blame?
By the way, you can express the same logic with a single type class adding more type parameters and conditional implicits (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]
}
Notice that syntax
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)
}
}
will not work.
Or you can make Out a type member rather than type parameter.
Well, I've just noticed that I actually didn't use T in my type class, so probably that's not what you wanted. You can try a single higher-kinded type class
abstract class IsArray[A, T: Numeric, Col[_], R <: Col[T], Out] {
def getRef(self: A, ref: R): Out
}
or just
abstract class IsArray[A, T: Numeric, Col[_], Out] {
def getRef(self: A, ref: Col[T]): Out
}
Here in our instances Col can be Id or List.