I have a class hierarchy in a package outside of my control that looks like this:
class Parent:
class Nested(a: Int):
???
I want some way to create a convenience class that wraps Nested, but so far, due to the dependent nature of Nested on its parent, I can only do it inside the body of the parent’s subclass like this:
new Parent:
case class MyNested(a: Int) extends Nested(a):
???
(new MyNested(1)).copy() // example use
The problem is that, if I want to extend Parent multiple times, I have to duplicate the convenience case class in the body of every subclass I create. In order to prevent having to copy this class over and over, I would like to define a class that works with any Parent#Nested Here’s my attempt at a solution:
case class MyNested(a: Int)(p: Parent) extends p.Nested(a):
???
new Parent:
self =>
MyNested(1)(self).copy()(self)
new Parent:
self =>
val m = MyNested(2)(self).copy()(self)
val n: Nested = m // problematic line
The problem is when I go to use a MyNested polymorphically where a Nested is expected, I get the error:
Found: MyNested
Required: Nested
I’m a Scala novice, so I’m sure there’s a better way to do what I’m asking. There is probably a theoretic reason that what I’m trying to do directly breaks the type system. In any case, I would greatly appreciate any help in the direction towards a “right” way to extend a nested class.
class Parent:
class Nested(a: Int):
???
final class Container[P <: Parent](val p: P):
case class MyNested(a: Int) extends p.Nested(a):
???
new Parent:
self =>
Container[this.type](this).MyNested(1).copy()
new Parent:
self =>
val c: Container[this.type] = Container(this)
val m = c.MyNested(2).copy()
val n: Nested = m // now this works
Thanks for the solution, this is exactly what I was looking for!
You’re right that the case class declaration as stated does not compile on 3.8.3 - it fails with non-private class MyNested refers to private value p, but if you change the declaration to private, it seems to compile fine.
I’ll note two things that were non-obvious to me about the solution, for my own sake and any others who might be interested:
the constructor parameter p needs to be val in order to be a stable path identifier
p must be upper-bounded by Parent and not a Parent itself - I’m guessing to allow for a singleton type to be used when creating the path-dependent type?
I haven’t looked deeply into the unsoundness bug you linked to - my takeaway is just that you can’t use a reference to a constructor parameter to specify the superclass of a class. I’m guessing the proposed workaround in this thread does not allow the same soundness bug, though I would need to think more about why it doesn’t.