1

I am trying to enforce a rule that the (dependent) return type of a typeclass, must itself implement a typeclass. So when the user implements the IsVec typeclass below, they must also ensure that the return value of the getElem method implements another typeclass (IsVecElem). My attempts to make this work look something like this:

// A typeclass for an vector element
abstract class IsVecElem[A, T: Numeric] {
  def dataOnly(self: A): T
}
// A typeclass for a vector
abstract class IsVec[A, T: Numeric] {
  // I would like this OutElem output type to implement the IsVecElem typeclass
  protected type OutElem
  def getElem(self: A, i: Int)(implicit tcO: IsVecElem[OutElem, T]): OutElem
}
// give this typeclass method syntax
implicit class IsVecOps[A, T: Numeric](value: A) {
  def getElem(i: Int)(implicit tcA: IsVec[A, T], tcO: IsVecElem[tcA.OutElem, T]) = tcA.getElem(value, i)
}

The problem comes with getElem in IsVecOps - this raises a compile error:

type OutElem in class IsVec cannot be accessed as a member of IsVec[A, T] from class IsVecOps. Access to protected type OutElem not permitted because enclosing class IsVecOps is not a subclass of class IsVec

Having IsVecOps extend IsVec isn't an immediate solution and doesn't feel like it should be, so I'm wondering if there's an error in approach elsewhere in the code.

Any help much appreciated.

Dmytro Mitin
  • 34,874
  • 2
  • 15
  • 47
Chris J Harris
  • 1,049
  • 7
  • 14
  • 2
    Why is `OutElem` **protected**? – Luis Miguel Mejía Suárez Sep 09 '20 at 01:42
  • @LuisMiguelMejíaSuárez - for no good reason - I was fiddling around trying to get it working earlier, and at the time that seemed to be what the compiler wanted. It doesn't have to be. – Chris J Harris Sep 09 '20 at 02:02
  • Ok, next question. Do you really need to enforce that implementors of **IsVec** must make their `OutElem` to have an instance of **IsVecElem**? It would be useful if you can expand on your use case, and how do you want users to use your typeclasses. – Luis Miguel Mejía Suárez Sep 09 '20 at 02:16
  • 2
    @LuisMiguelMejíaSuárez - it's probably best explained by an earlier question I asked, which had a more detailed example: https://stackoverflow.com/questions/63769737/typeclass-constraint-in-typeclass-generic/63773982#63773982 Essentially I'm trying to create a typeclass that can be implemented by anything that can potentially act as an array. So the `Is2dArray` typeclass should have a `get` or `slice` method that returns something that implements an `Is1dArray` typeclass. – Chris J Harris Sep 09 '20 at 02:24
  • 1
    This `(implicit tcA: IsVec[A, T],tcO: IsVecElem[tcA.OutElem, T])` will never work because one parameter (`tcO`) cannot be dependent on another parameter (`tcA`) _of the same parameter group_. You could put them in separate parameter groups but then they can't both be implicit parameters. – jwvh Sep 09 '20 at 02:31
  • @jwvh - yes, that makes sense. I tried both iterations with no success. I take it from the fact that you are not suggesting a workaround, that there is no obvious workaround here? – Chris J Harris Sep 09 '20 at 03:35
  • @Chrisper; No. I'm unaware of any obvious workaround, but then, it's never been clear to me what, exactly, you're trying to achieve. – jwvh Sep 09 '20 at 04:15
  • @jwvh - Not sure if you're necesarily interested, but I'll add a basic readme to the github repo and post a link here, when I have a moment. It's a niche hobby project which I don't necessarily expect to be useful professionally. I don't necessarily think I'm structuring it in the most practical way, but it has been useful (with your help) for me to get to understand the type system and typeclasses better. – Chris J Harris Sep 09 '20 at 04:33
  • @jwvh *"because one parameter (`tcO`) cannot be dependent on another parameter (`tcA`) of the same parameter group"* This is standard situation in type-level programming in Scala with type classes. `Aux`-types are precisely for that. – Dmytro Mitin Sep 09 '20 at 08:28
  • 1
    @jwvh - see here: https://github.com/chrisharriscjh/sport-array – Chris J Harris Sep 09 '20 at 09:39

1 Answers1

2

IsVecOps shouldn't extend IsVec. Implicit class (with the only purpose to introduce an extension method) extending a type class would be very strange.

If for a moment you remove access modifier (protected) you'll see that the error message changes to

illegal dependent method type: parameter may only be referenced in a subsequent parameter section
    def getElem...

Try to add a type parameter (OE) and specify type refinement (IsVec[A, T] { ... })

implicit class IsVecOps[A, T: Numeric](value: A) {
  def getElem[OE](i: Int)(implicit tcA: IsVec[A, T] { type OutElem = OE }, tcO: IsVecElem[OE, T]): OE = tcA.getElem(value, i)
}

If you introduce Aux-type

object IsVec {
  type Aux[A, T, OE] = IsVec[A, T] { type OutElem = OE }
}

then you can rewrite type refinement more compactly

implicit class IsVecOps[A, T: Numeric](value: A) {
  def getElem[OutElem](i: Int)(implicit tcA: IsVec.Aux[A, T, OutElem], tcO: IsVecElem[OutElem, T]): OutElem = tcA.getElem(value, i)
}

How can I have a method parameter with type dependent on an implicit parameter?

When are dependent types needed in Shapeless?

Why is the Aux technique required for type-level computations?

Understanding the Aux pattern in Scala Type System

In Dotty you'll be able to use trait parameters, extension methods, multiple implicit parameter lists, types of parameters in the same parameter list dependent on each other:

trait IsVecElem[A, T: Numeric] {
  def dataOnly(self: A): T
}
  
trait IsVec[A, T: Numeric] {
  protected type OutElem
  def (self: A) getElem(i: Int)(using IsVecElem[OutElem, T]): OutElem
}

or

trait IsVecElem[A, T: Numeric] {
  def dataOnly(self: A): T
}

trait IsVec[A, T: Numeric] {
  /*protected*/ type OutElem
  def getElem(self: A, i: Int)(using IsVecElem[OutElem, T]): OutElem
}
extension [A, T: Numeric](value: A) {
  def getElem(i: Int)(using tcA: IsVec[A, T], tcO: isVecElem[tcA.OutElem, T]) = tcA.getElem(value, i)
}

(tested in 0.28.0-bin-20200908-ce48f5a-NIGHTLY)

Dmytro Mitin
  • 34,874
  • 2
  • 15
  • 47