(using scala 2.11.12)
Why does this compile?
sealed trait Inner
sealed trait Outer {
sealed trait I extends Inner
}
case object OuterA extends Outer {
case object Inner1 extends I
case object Inner2 extends I
}
case object OuterB extends Outer {
case object Inner1 extends I
}
class Data[O <: Outer](outer: O, inner: O#I)
// I expected this not to compile but it actually does
val data = new Data(OuterA, OuterB.Inner1)
Why does this not compile?
sealed trait Inner
sealed trait Outer {
type I <: Inner
}
case object OuterA extends Outer {
sealed trait OuterAInner extends Inner
override type I = OuterAInner
case object Inner1 extends OuterAInner
case object Inner2 extends OuterAInner
}
case object OuterB extends Outer {
sealed trait OuterBInner extends Inner
override type I = OuterBInner
case object Inner1 extends OuterBInner
}
class Data[O <: Outer](outer: O, inner: O#I)
// I expected this to compile but it actually does not
val data = new Data(OuterA, OuterA.Inner1)
// type mismatch;
// found : com.transparencyrights.ermine.model.V1.OuterA.Inner1.type
// required: ?#I
// Note that Inner1 extends Any, not AnyRef.
// Such types can participate in value classes, but instances
// cannot appear in singleton types or in reference comparisons.
// val data = new Data(OuterA, OuterA.Inner1)
What I want to achieve is a unique Data
constructor that takes two arguments, an Outer
and an Inner
, with the Inner
type restricted to an Inner
subtype dependent on the given Outer
instance.
On both cases the problem is that O#I
does not do what you want it to do.
It really does not refer to the I
inside the specific O
you have, but rather to the generic one inside Outer
.
You can fix both snippets using Path dependent types and Generalized type constraints.
sealed trait Inner
sealed trait Outer {
sealed trait I extends Inner
}
final case object OuterA extends Outer {
final case object Inner1 extends I
final case object Inner2 extends I
}
final case object OuterB extends Outer {
final case object Inner1 extends I
}
final class Data[O <: Outer, I <: O#I] private (outer: O, inner: I)
object Data {
final def apply[O <: Outer, I <: O#I](outer: O, inner: I)(implicit ev: I <:< outer.I): Data[O, I] =
new Data(outer, inner)
}
val data = Data(OuterA, OuterB.Inner1) // Does not compile.
val data = Data(OuterA, OuterA.Inner1) // Does compile.
sealed trait Inner
sealed trait Outer {
type I <: Inner
}
final case object OuterA extends Outer {
override final type I = OuterAInner
sealed trait OuterAInner extends Inner
final case object Inner1 extends OuterAInner
final case object Inner2 extends OuterAInner
}
final case object OuterB extends Outer {
override final type I = OuterBInner
sealed trait OuterBInner extends Inner
final case object Inner1 extends OuterBInner
}
final class Data[O <: Outer, I <: O#I] private (outer: O, inner: I)
object Data {
final def apply[O <: Outer, I <: O#I](outer: O, inner: I)(implicit ev: I <:< outer.I): Data[O, I] =
new Data(outer, inner)
}
val data = new Data(OuterA, OuterA.Inner1) // Does compile.
val data = new Data(OuterA, OuterB.Inner1) // Does not compile.
Now, since the examples does not show how do you want to use the Data class and the outer & inner fields, this may not be enough, but hope it helps you.
If you have any doubts, do not hesitate on asking on the comments.