2

Following sample code fails to compile with the error listed below.

class Outer {
  class Inner

  val instance: Inner = new Inner

  def verify(pd: Inner): Boolean = pd == instance
}

class UseOuter(val pdp: Outer) {
  def foo: pdp.Inner = pdp.instance
}

class UseCase {
  val pdp = new Outer
  val user = new UseOuter(pdp)

  val i = user.foo

  pdp.verify(i)
}

Error:

test.sc:19: type mismatch;
 found   : UseCase.this.user.pdp.Inner
 required: UseCase.this.pdp.Inner
  pdp.verify(i)
             ^
Compilation Failed

I'm not entirely sure but reading Inner Classes suggests that it's the intended behaviour? Specifically the following sentence:

As opposed to Java-like languages where such inner classes are members of the enclosing class, in Scala such inner classes are bound to the outer object.

If that's the case and this is indeed the desired behaviour, is there a way to encode this requirement in Scala?

There are a few similar questions but they all concern inner types, and not classes and the proposed solutions will not apply.

Brian McCutchon
  • 8,354
  • 3
  • 33
  • 45
racetrack
  • 3,766
  • 30
  • 30
  • 2
    Actually the very documentation you reference has an answer at the end where Scala is compared to Java and `Graph.Node` is compared to `Graph#Node` – SergGr Jan 08 '19 at 18:06

2 Answers2

3

Here's an approach that maintains the safety of path-dependent types for those times when you want it. First, modify UseOuter to take the type of pdp as a type parameter:

class UseOuter[O <: Outer](val pdp: O) {
  def foo: pdp.Inner = pdp.instance
}

Then, when you instantiate UseOuter, explicitly pass pdp.type as the type parameter:

val user = new UseOuter[pdp.type](pdp)

This allows the compiler to remember that the pdp in UseOuter is the same pdp in UseCase, so the types match up correctly.

Brian McCutchon
  • 8,354
  • 3
  • 33
  • 45
  • 3
    You can improve the ergonomics of this a little by defining `pdp` as `object pdp extends Outer`, in which case `new UseOuter(pdp)` will infer the singleton type without needing the explicit type argument. – Miles Sabin Jan 09 '19 at 10:06
2

For example you can use type projections:

class Outer {
  class Inner

  val instance: Inner = new Inner

//  def verify(pd: Inner): Boolean = pd == instance
  def verify(pd: Outer#Inner): Boolean = pd == instance
}

class UseOuter(val pdp: Outer) {
//  def foo: pdp.Inner = pdp.instance
  def foo: Outer#Inner = pdp.instance
}

class UseCase {
  val pdp: Outer = new Outer
  val user: UseOuter = new UseOuter(pdp)

//  val i: user.pdp.Inner = user.foo
  val i: Outer#Inner = user.foo

  pdp.verify(i)
}
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • 1
    I think the technically you don't need to change the type of `instance` to `Outer#Inner`, only in the `foo` and `verify`. Cast from `outerInstance.Inner` to `Outer#Inner` is always safe and automatic. – SergGr Jan 08 '19 at 18:03