Scala: overriding a value of a generic, existential type with an instantiated type

I have a general MappingPath trait that is invariant with respect to its type parameters:

trait MappingPath[X<:AnyMapping, Y<:AnyMapping]

      

and a factory interface for it:

trait Pathfinder[X, Y] {
    def apply(fun :X=>Y) :MappingPath[_<:AnyMapping,_<:AnyMapping]
    def get(fun :X=>Y) :Option[MappingPath[_<:AnyMapping, _<:AnyMapping]]
}

      

I am running a skeleton implementation that works for one mapping:

class MappingPathfinder[M<:AnyMapping, X, Y] extends Pathfinder[X, Y] {
   override def apply(fun :X=>Y) :MappingPath[M, _<:AnyMapping] = ???
   override def get(fun :X=>Y) :Option[MappingPath[M, _<:AnyMapping]] = ???
}

      

which throws a compile error complaining that MappingPathfinder.apply

nothing overrides or implements anything Pathfinder.apply

. Interestingly, by replacing M

by _<:AnyMapping

in the apply

return type, it compiles, and there are no complaints about such a method get

.

What's happening? I am using scala 2.11.5.

EDIT: I was able to work around my problem by adding explicit entity annotations:

//Pathfinder
def apply(fun :X=>Y) :MappingPath[A, B] forSome { type A<:AnyMapping; type B<:AnyMapping }

//MappingPathfinder
def apply(fun :X=>Y) :MappingPath[A, B] forSome { type A>:M<:M; type B<:AnyMapping }

      

This seems to work, i.e. I can do:

(p :MappingPath[_<:AnyMapping, M]) ++ mappingPathfinder(f),

      

where ++

a path is required starting with the same type as this

. It looks a little silly and, of course, confusing.

+3


source to share


1 answer


Not an answer, but your use case could be simplified:

trait Higher[U]

trait Super {
  def foo: Higher[_]
}

trait Sub[M] {
  override def foo: Higher[M]  // error: method foo overrides nothing
}

      


Instead of existential types, I would use a type member:



trait Super {
  type U
  def foo: Higher[U]
}

trait Sub[M] {
  type U = M
}

      

I think the difference is that in the case of an existential type, you only specify that the return type of the parameter has some upper bound, but not necessarily that it is always the same type; whereas in my second example type U

means it will end up being one specific type and you can only qualify for a specific type. You can make the upper bounds more precise:

trait Upper

trait A {
  type U <: Upper  
}

trait Specific extends Upper

trait B extends A {
  type U <: Specific   // type is "overridden"
}

      

If possible, I would avoid existential types, and your case seems to be the perfect help for such avoidance. In most cases, existential types are only needed for Java interop.

+2


source







All Articles