Why is it possible to define a class with a unreified path-dependent type? - scala

Considering the following example:
trait Supe {
type Out <: Supe
def out: Out
}
class Reif1 extends Supe {
type Out = Reif1
override def out: Out = this
}
class Reif2 extends Supe {
type Out >: this.type <: Reif2
override def out: Out = this
}
class Reif1 should obviously work. as type Out is reified and becomes a type alias
class Reif2 also works but seriously? type Out only has upper/lower bound defined and the bound was not even tight enough: this.type is a singleton type, and Reif2 is a class type. So what exactly would Out look like if Reif2 got instantiated? Is it going to be this.type? Or Reif2? But the bigger question should be: why scalac 2.12/2.13 allows it be compiled?

In Scala (or in DOT calculus 1 2) all types are intervals.
type Out = Reif1 is (or is supposed to be) type Out >: Reif1 <: Reif1.
Abstract type without bounds type Out is type Out >: Nothing <: Any.
So what exactly would Out look like if Reif2 got instantiated?
It will remain exactly type Out >: this.type <: Reif2
val r = new Reif2
import scala.reflect.runtime.universe._
typeOf[r.Out] // App.r.Out
showRaw(typeOf[r.Out]) // TypeRef(SingleType(ThisType(App), TermName("r")), TypeName("Out"), List())
typeOf[r.Out].typeSymbol.isAbstract // true
typeOf[r.Out].typeSymbol.typeSignature // >: Reif2.this.type <: App.Reif2
If you replace type Out >: this.type <: Reif2 in Reif2 with type Out = this.type (or type Out = Reif2 or type Out = Supe) then isAbstract will return false.
Abstract type member of a singleton object (see application of abstract type)
What is the meaning of a type declaration without definition in an object? (see why it's not easy to check that type T >: L <: U is not abstract)
Concrete classes can have abstract type members #1753
SI-8217 allow abstract type members in objects #4024
Abstract type members are incorrectly forbidden in objects (unless inherited) #8217
Use of abstract type in a concrete class?
Concrete classes with abstract type members
What's different between "def apply[T](c:T)" and "type T;def apply(c:T)"

Related

Existential Higher Kinded Method Parameter

I have the following types defined:
trait Context
trait Attribute[C <: Context]
trait AttributeDefinition[A[_ <: Context] <: Attribute[C] forSome { type C <: Context }] {
def read[C <: Context]: A[C]
}
I want AttributeDefinition to return an Attribute parameterized by a context. Each definition applies to only one type of Attribute. Hypothetically:
class ConstantValueAttribute[C <: Context] extends Attribute[C]
object ConstantValueAttributeDefinition extends AttributeDefinition[ConstantValueAttribute] {
override def read[C <: Context]: ConstantValueAttribute[C] = ???
}
This totally compiles fine, but i have trouble crafting method signatures that accept an unbounded AttributeDefinition. As well as creating a collection of AttributeDefinitions. All the following fail:
def def1(attribute: AttributeDefinition): Unit = ???
def def2(attribute: AttributeDefinition[_]): Unit = ???
def def3(attribute: AttributeDefinition[_ <: Attribute[C] forSome { type C <: Context }]): Unit = ???
def def4(attribute: AttributeDefinition[A[_]] forSome { type A <: Attribute[C]; type C <: Context })
EDIT:
I am trying to find a method declaration like the above that will compile. I also am looking to figure out how to declare a collection of AttributeDefinitions.
val val1 = new mutable.ArrayBuffer[AttributeDefinition]
if that is not possible i would love to figure out how to simplify the traits above that can still capture the simple read method declaration.
Proposal with a type member
Essentially, what you want in both defN-declarations and Seq-collections of AttributeDeclaration is a higher kinded existential type for Attribute that is used as an argument for AttributeDeclaration. So, ideally, you would like to have:
def def1(attrDef: AttributeDeclaration[A] forSome {
type A[C <: Context] <: Attribute[C] }): Unit = ???
However, this does not work, and this is an unresolved issue since 2015, so chances are that it won't be fixed until dotty.
Instead of trying to force the compiler to work with higher kinded existentials so that you can throw away the unnecessary type parameter, I would propose that you don't add the annoying type parameter in the first place. Consider these definitions (compiles with 2.12.4):
trait Context
trait Attribute[C <: Context]
trait AttributeDefinition {
type A[C <: Context] <: Attribute[C]
def read[C <: Context]: A[C]
}
class ConstantValueAttribute[C <: Context] extends Attribute[C]
object ConstantValueAttributeDefinition extends AttributeDefinition {
type A[C <: Context] = ConstantValueAttribute[C]
def read[C <: Context]: ConstantValueAttribute[C] = ???
}
def def1(attrDef: AttributeDefinition): Unit = ???
val collDefs: Seq[AttributeDefinition] = List(ConstantValueAttributeDefinition)
These definitions have following properties:
Context and Attribute are left unchanged
A becomes a type member of AttributeDefinition, so it does not have to be forcefully erased by an existential later
read-signature is completely unchanged, it's literally the same as in your original code, character for character
declaration of the simplest and most intuitive def1 works immediately without changes (also essentially copied from your code 1:1)
declaration and definition of collections with AttributeDefinitions also works just as smoothly.
Recall that you don't even lose anything if you move something from type parameter to type member, because you can always refer to the type member by name (for example, when you want to impose an additional restriction on that type member). The following minimal example is supposed to demonstrate how to impose additional restrictions on type members (vaguely related to your code, uses Context):
trait Foo {
type Member
}
def restrictiveFoo(x: Foo { type Member <: Context }): Unit = ???
Here, you can force the argumen Foo to have type member that derives from Context. It would work with type Member = Context similarly. What I wanted to demonstrate by that: it's not like type members are forever hidden inside the trait, if you introduce them. You can do with them almost all the same things as with type parameter. To summarize:
Adding additional constraints on a (higher kinded) type member is easy
Erasing a superfluous type parameter with higher kinded existentials doesn't seem to work at all.
So, therefore, I would advise to use a type member.
Proposal with change of variance from A to +A
Another quick-fix to make it compile:
trait Context
trait Attribute[C <: Context]
trait AttributeDefinition[+A[X <: Context] <: Attribute[X]] {
def read[C <: Context]: A[C]
}
class ConstantValueAttribute[C <: Context] extends Attribute[C]
object ConstantValueAttributeDefinition
extends AttributeDefinition[ConstantValueAttribute] {
override def read[C <: Context]: ConstantValueAttribute[C] = ???
}
class FooValueAttribute[C <: Context] extends Attribute[C]
object FooValueAttributeDefinition
extends AttributeDefinition[FooValueAttribute] {
override def read[C <: Context]: FooValueAttribute[C] = ???
}
def def1(attrDef: AttributeDefinition[Attribute]): Unit = {}
def1(ConstantValueAttributeDefinition)
val listOfDefs: List[AttributeDefinition[Attribute]] = List(
ConstantValueAttributeDefinition,
FooValueAttributeDefinition
)
inspired by the answer to this question. Strangely enough, it's seems to be almost exactly the same constellation. It didn't solve the problem with higher kinded existentials though, instead the variance of A has been changed to +A, so that one could use Attribute as least upper bound of all concrete As.
The correct signature would be
def `def`[A[_ <: Context] <: Attribute[C] forSome { type C <: Context }](attribute: AttributeDefinition[A]) = ???
You need to use a type parameter for your function, which should have (at least) the same constraints as the one in your type.
Note: in my opinion this is a reason why you should avoid putting type constraints on the type arguments for your type, they should only be put on functions/operations instead.
trait Context
trait Attribute[+Context]
trait AttributeDefinition[Attribute] {
def read[Context]: Attribute
}
class ConstantValueAttribute extends Attribute[Context]
object ConstantValueAttributeDefinition extends AttributeDefinition[ConstantValueAttribute] {
def read[Context]: ConstantValueAttribute = ???
}
def def1[C](attribute: AttributeDefinition[C]) : Unit = ???
As a hint for simplification too.

Constrain higher-kinded path-dependent type like a self-type

I am trying to replace a type defined with 2 type parameters and a self-type, with a simpler version based on a single path-dependent type.
My problem is that I can't express the same constraints. I think a full example will be easier to understand.
class Attribute
// Test 1: Using a 2nd higher-kinded parameter and a self-type
// Advantage: Subclasses are constrained by the self-type to specify the "This" parameter correctly
// Drawback: Container1 has a 2nd type parameter and "AnyContainer1" type is really ugly
sealed trait Container1[+A <: Attribute, +This[+X <: Attribute] <: Container1[X, This]] { self: This[A] => }
class Foo[+A <: Attribute] extends Container1[A, Foo]
//class Bar[+A <: Attribute] extends Container1[A, Foo] // Won't compile :-)
object Container1 {
// This is ugly...
type AnyContainer1[+X <: Attribute] = F[X] forSome { type F[+Y <: Attribute] <: Container1[X, F] }
// type AnyContainer1[+X <: Attribute] = Container1[X, AnyContainer1] // This doesn't compile :-(
}
// Test 2: Using path-dependent types
// Advantage: Container2 has a single type parameter and AnyContainer2 is easily expressed
// Drawback: The path-dependent parameter can be overridden in a bad way
sealed trait Container2[+A <: Attribute] {
protected type This[+B <: Attribute] <: Container2[B]
// Note that I need `This` to take a generic parameter in
// order to declare methods like `def foo[T]: This[T]` in subclasses.
}
class Bar[+A <: Attribute] extends Container2[A] {
override protected type This[+X <: Attribute] = Bar[X]
}
class Baz[+A <: Attribute] extends Container2[A] {
// Will compile even though `Bar != Baz`, I'd like to enforce This = Baz somehow...
override protected type This[+X <: Attribute] = Bar[X]
}
object Container2 {
type AnyContainer2[+X <: Attribute] = Container2[Attribute] // Easy :-)
}
Is there a better way to constrain the This type while using path-dependent types to keep things clean? With the next version of Scala (Dotty for now), existential types will disappear, and should be replaced with path-dependent ones, so how do I implement this without existential types?

scala - yet another type mismatch

type mismatch; found : IDataContext[_$1] where type _$1 <: DBObject required: IDataContext[DBObject] Note: _$1 <: DBObject, but class IDataContext is invariant in type A. You may wish to define A as +A instead. (SLS 4.5)
This problematic part "required: IDataContext[DBObject]" is actually defined like this.
abstract class IDataContextBuilder[+A <: DBObject] {
def initXDataPoints(dataContext: IDataContext[A]): Unit
}
I changed it to following and it works but it looks quite redundant
abstract class IDataContextBuilder[+A <: DBObject] {
def initXDataPoints[A <: DBObject](dataContext: IDataContext[A]): Unit
}
Now issue is in implementation of above method:
override def initXDataPoints[Dim1Agg](dataContext: IDataContext[Dim1Agg]): Unit = { ...}
method initXDataPoints overrides nothing. Note: the super classes of class Dim1DataContextBuilder contain the following, non final members named initXDataPoints: def initXDataPoints[A <: DBObject](dataContext: IDataContext[A]): Unit
Dim1Agg is subtype of DBObject
Looking at the first code snippet I think you have a variance problem: Type parameter A is covariant (defined with a +) however it appears in a contra-variant position - the argument dataContext to the method initXDataPoints.
What you can do is parameterize initXDataPoints with an lower bound like that:
def initXDataPoints[B >: A](dataContext: IDataContext[B]): Unit
Edit:
trait DBObject
trait Dim1Agg extends DBObject
trait IDataContext[A]
abstract class IDataContextBuilder[+A <: DBObject] {
def initXDataPoints[B>:A](dataContext: IDataContext[B]): Unit
}
class Dim1DataContextBuilder extends IDataContextBuilder[Dim1Agg] {
override def initXDataPoints[B >: Dim1Agg](dataContext: IDataContext[B]): Unit = ???
}
The rationale behind adding the lower bound type parameter B is that the implementation will not be able to use any specific properties of the type A. Using specific properties of A may break under inheritance and therefore are disallowed by the scala compiler. This is explained very well in the book Programming in Scala 2nd edition chapter 19 (Type parameterization). I suggest to read the whole chapter, and look for specific example that explains this in section 19.4.

Type aliasing type constraints in generics

I have a situation where I want to use a bounded generic type as a constraint on what class can be produced. The problem is that I need to
abstract class SomeAbstractClass
trait Foo[A <: SomeAbstractClass]
trait Bar[A] extends Foo[A]
// Fails error: type arguments [A] do not conform to trait Foo's type parameter bounds [A <: SomeAbstractClass]
// Need to write it like this, for every single subclass of Foo
trait Bar[A <: SomeAbstractClass] extends Foo[A]
Is there an easier way to promote that through the system without having to retype the bounds every time?
Constraints on type parameters are constraints. They don't propagate transitively via inheritance as you would like them to.
Perhaps this is applicable or produces some new ideas at least:
abstract class SomeAbstractClass
trait Foo { // propagated by abstract type member
type A <: SomeAbstractClass
}
trait Bar extends Foo // no generic type parameter needed here
trait BAR[SAC <: SomeAbstractClass] extends Bar { type A = SAC } // introduce type parameter
trait Baz[SAC <: SomeAbstractClass] extends BAR[SAC] // generic type parameter needed here

Exchanging a type parameter's upper bound for an evidence parameter

I want to relax the constraints on a trait's type parameter and instead impose them on a method in the form of an evidence parameter. Given some skeletal setup:
trait State[Repr]
object Observer {
def apply[Repr <: State[Repr]](reader: Reader[Repr]): Observer[Repr] =
new Observer[Repr] {}
}
trait Observer[A]
trait Reader [A]
This works:
trait StateX[Repr <: StateX[Repr]] extends State[Repr] {
protected def reader: Reader[Repr]
def observe: Observer[Repr] = Observer(reader)
}
And this doesn't:
trait StateY[Repr] extends State[Repr] {
protected def reader: Reader[Repr]
def observe(implicit ev: Repr <:< State[Repr]): Observer[Repr] = Observer(reader)
}
With message "inferred type arguments [Repr] do not conform to method apply's type parameter bounds [Repr <: State[Repr]]". Since the evidence ev suggests this conformation, I wonder how StateY can be fixed.
Your problem is that even though evidence of the form A <:< B implies that a value of type A can be converted to a value of type B it doesn't imply A <: B ... indeed, the main reason for using a type constraint or a view bound rather than an ordinary type bound is precisely because such a subtype relationship doesn't hold.
Consequently the constraint in StateY, Repr <:< State[Repr], isn't sufficient to satisfy the bound Repr <: State[Repr] on Observer's apply method. Given that you want to relax the constraint on StateX's type parameter your only option is to weaken the constraint on the apply method's type parameter correspondingly. That gets you something like the following using a view bound instead of an ordinary type bound,
object Observer {
def apply[Repr <% State[Repr]](reader : Reader[Repr]) : Observer[Repr] =
new Observer[Repr] {}
}
or alternatively,
object Observer {
def apply[Repr](reader : Reader[Repr])
(implicit ev : Repr <:< State[Repr]) : Observer[Repr] =
new Observer[Repr] {}
}
if you'd rather use constraints throughout.
The evidence suggests that a Repr can be converted to State[Repr]. It says nothing about what can be done with a Reader[Repr].
There is no general method (independant of T) to convert T[A] to T[B] given an A => B. That might be possible for a covariant T, but there is no such thing in the language.