After two years of deliberations, I have come to the conclusion that the inability to tell Scala's subsumption not to subsume the input type parameter on methods of covariant types can not be worked around in any way.
And it prevents correct implementation of certain features which also can not be worked around in any other way. I really wanted to mark this as a bug, instead of an improvement, because it blocks essential functionality that can not be achieved any other way. Feel free to change it to a bug if you agree with me. It is said that if a type system can't correctly type the "contains problem" then it is not worth going through the effort to enforce types at all at compile-time. Refer to the following links for the proof and example use cases which support this.
http://stackoverflow.com/questions/8360413/selectively-disable-subsumption-in-scala-correctly-type-list-contains
Note it is not just contains which is affected yet it is the ability to correct type anything that is an input to a covariant type:
https://groups.google.com/d/msg/scala-debate/LWBz3-Q0pNI/rWG4hImvCQgJ
https://groups.google.com/d/msg/scala-language/lChLcLES_Dk/b78ywjsHcbMJ
You're probably not aware of what I added to -Xlint over a year ago (but that still means it's only in 2.11.) I forgot about it myself. In my own code, if Any or AnyVal is inferred, it's invariably a mistake. For the very few occasions when I use one of those types, I'm willing to suck it up and type the few extra characters. This doesn't solve your problem, unless maybe you are very brave and combine it with -Xfatal-warnings, but may expand your options.
% scala3 -XlintWelcome to Scala version 2.11.0-20130917-220248-d45a3c8cc8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_40).Type in expressions to have them evaluated.Type :help for more information.
scala> List(1, "a")<console>:8: warning: a type was inferred to be `Any`; this may indicate a programming error.List(1, "a")^res0: List[Any] = List(1, a)
scala> List(1) contains "a"<console>:8: warning: a type was inferred to be `Any`; this may indicate a programming error.List(1) contains "a"^res1: Boolean = false
scala> List(1) contains 'a'<console>:8: warning: a type was inferred to be `AnyVal`; this may indicate a programming error.List(1) contains 'a'^res2: Boolean = false
scala> List(1) contains 1res3: Boolean = true
scala>
--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
-- Francois ARMAND http://rudder-project.org http://www.normation.com
You're probably not aware of what I added to -Xlint over a year ago (but that still means it's only in 2.11.) I forgot about it myself. In my own code, if Any or AnyVal is inferred, it's invariably a mistake. For the very few occasions when I use one of those types, I'm willing to suck it up and type the few extra characters. This doesn't solve your problem, unless maybe you are very brave and combine it with -Xfatal-warnings, but may expand your options.
On Wednesday, September 18, 2013 11:22:18 PM UTC+8, Paul Phillips wrote:You're probably not aware of what I added to -Xlint over a year ago (but that still means it's only in 2.11.) I forgot about it myself. In my own code, if Any or AnyVal is inferred, it's invariably a mistake. For the very few occasions when I use one of those types, I'm willing to suck it up and type the few extra characters. This doesn't solve your problem, unless maybe you are very brave and combine it with -Xfatal-warnings, but may expand your options.At the scala-debate thread I linked in the OP, Justin Decour was making the same point that collections of Any are invariably a design error and I agreed with him of course. Does any one disagree?As you admit and as far as I can see w.r.t. to the issue I raise, your warning can only tell me where to insert boilerplate at the call site to work around subsumption. See my answer at SO for an example of the boilerplate that needs to be written at the call site to get desired functionality. I don't consider such boilerplate a viable work around at all.The problem I am raising now is that there is no way to write a method for a container (not just collections) which refuse to input values that is are neither a supertype nor subtype of the covariant type parameter of the container. I think everyone assumes that this is because the Liskov Substitution Principle requires that the input value have a type that is *equal* or a supertype of the covariant type parameter of the container.The *equal* is I think the salient factor to recognize. If no subsumption is performed on that input value type, then only supertypes and subtypes can fulfill that equal or supertype criteria.
In my SO example, Int as the input value type is not a super nor subtype of String as the element type, unless the Int is subsumed to Any and then it is a supertype of String.So my proposed fix is some new syntax to declare at the method definition site that the input value type should not be subsumed to any supertype, only to *equal* to the covariant element type. With my proposed fix, if the input value type is a supertype without subsumption, then that is fine and the method is subsumed to that type.
So with this proposed fix implemented at a definition site, the call site can still override it with a cast on the input value argument.
The discussion at the scala-debate thread linked in the OP, explains how this problem makes it impossible to overload the + operator for both append and concatenation. This is a clue that this problem may have far reaching implications and probably infects the libraries in more ways than the two major cases enumerated thus far.
Just write instead and be honest with yourself:trait List[+A] {def contains(x: Any): Boolean}
I mean that if you want to force the method to subsume to Any, then input an Any.
So with this proposed fix implemented at a definition site, the call site can still override it with a cast on the input value argument.I mean that if you want to force the method to subsume to Any, then input an Any. As Paul wrote, in those rare cases you want this misbehavior then you can write a tiny bit of boilerplate to get it.
--
It is true that subsumption reduces boilerplate, yet at the cost of inability to exclude any type. That is why I propose to make it a definition site feature, so library designers can weigh the tradeoff.
If we had first-class unions, perhaps we would want another per method attribute to tell compiler to subsume to the union.
I am thinking the guidance needs to be per method to be useful, otherwise as you said, you can't pick any target, the problem just shifts up the hierarchy.
On Thursday, September 19, 2013 1:15:00 AM UTC+8, Paul Phillips wrote:On Wed, Sep 18, 2013 at 10:12 AM, Shelby <she...@coolpage.com> wrote:
If we had first-class unions, perhaps we would want another per method attribute to tell compiler to subsume to the union.How to communicate that you want a union type to be inferred vs. wanting subsumption to take place - and more importantly, how NOT to have to communicate this, because we'd very much like it to magically do what we want on an inference by inference basis - and how the tax might take shape is one of the great open questions about how union types would work out in actual practice.Why not annotate the method as follows?
trait List[+A] {def append[B >: A](x: B): List[B] // accepts subsumption (to Any or if we have unions then to the union)
def contains[B >= A](x: B): Boolean // denies subsumption}
trait Seq[+T] { def contains[S](this: Seq[S])(v: S): Boolean ^ | +-- implicit this parameter, must be 1st when declared }It allows to "capture" the the type of this as seen at the call-site by the typer. When we have:
val l = List(1, 2, 3) l.contains("a")the typer infers S = Int and reports an error. One could still do (l: List[Any]).contains("a"). AFAICS there is no substitution, and thus no violation of LSP. I believe @uncheckedVariance could be removed from the std-lib. Some other use cases are listed here:
IMO, one solution is to allow to explicitly "type" the implicit this parameter in method definitions, like e.g.:
trait Seq[+T] { def contains[S](this: Seq[S])(v: S): Boolean
^ | +-- implicit this parameter, must be 1st when declared }It allows to "capture" the the type of this as seen at the call-site by the typer. When we have:
val l = List(1, 2, 3) l.contains("a")the typer infers S = Int and reports an error. One could still do (l: List[Any]).contains("a"). AFAICS there is no substitution, and thus no violation of LSP. I believe @uncheckedVariance could be removed from the std-lib. Some other use cases are listed here:
http://permalink.gmane.org/gmane.comp.lang.scala.internals/14123
I not good a politics. I put my foot in my mouth often.
I want to push for consensus, because this problem is major and fundamental (concerning correctness). And it is ridiculous that this problem has been around so long in a language that claims to be one of the most sophisticated for static typing.
Unfortunately, your answer is somewhat incorrect, and only achieves the opposing functionality as Kim Stebel's and my answer.(new Super) contains (new Sub)
correctly does not generate an error. However,(new Sub) contains (new Super)
won't compile! Same but opposing problem that I discovered in my answer, c.f. the typesSuper
andSub
in my answer. And your solution has high overhead, and Stebel's is not as well integrated, thus my answer remains the best so far. The only solution is as I have described the my UPDATE on my question.
Note that as far as I know Ceylon solves this problem with their subsumption to the inferred union. So we have competition on the horizon and I am hoping we can get this fixed in Scala before Ceylon gains momentum. The solution I have proposed is better than what Ceylon can do, because it gives us the per method annotation to subsume or not subsume. Also my proposal is orthogonal to the implementation of first-class unions, i.e. it can subsume to Any in the meantime when the method annotation is to accept subsumption.
Thus realize that the notion that all types have a supertype of Any thus they are really Any is the wrong way to conceptualize a static typing system. They can subsume to Any, but they are not Any until they do. And the rules of subsumption are never subsume the destination type unless the destination type is a covariant parameter.Thus we can see the only time that subsumption to a mutual upper bound of Any can occur is when the destination type is a covariant parameter. Thus the solution is to focus on where that is the case, and selectively turn off the mutual subsumption of the destination covariant type parameter as I have done in my proposed solution.Never we will want to turn off the subsumption of the source type as that would be the antithesis of subtyping.
On Wed, Sep 18, 2013 at 11:21 PM, Jan Vanek <j3v...@gmail.com> wrote:
> trait Seq[+T] {
> def contains[S](this: Seq[S])(v: S): Boolean
> ^
> |
> +-- implicit this parameter, must be 1st when declared
> }
You can have that already now:
implicit class SeqContains[T](seq: Seq[T]) { def contains(t: T): Boolean = ... }
Well yes you just copied Vlad's solution, as I said JV and Vlad's solution both turn off subtyping in the covariant direction, i.e. you can't pass a subtype of T
not using an upper bound T instead of a lowerbound.
On Wed, Sep 18, 2013 at 9:39 AM, Shelby <she...@coolpage.com> wrote:
I mean that if you want to force the method to subsume to Any, then input an Any.
I have argued many times that Any and AnyVal should never be inferred. A legitimate counterargument is that this only serves to push the problem upward to the next point of type intersection - it doesn't address it in any fundamental way. Next up on the block: "Product with Serializable".
scala> List(None, Nil)
res0: List[Product with Serializable] = List(None, List())
On Wed, Sep 18, 2013 at 9:39 AM, Shelby <she...@coolpage.com> wrote:
I mean that if you want to force the method to subsume to Any, then input an Any.
I have argued many times that Any and AnyVal should never be inferred. A legitimate counterargument is that this only serves to push the problem upward to the next point of type intersection - it doesn't address it in any fundamental way. Next up on the block: "Product with Serializable".
scala> List(None, Nil)res0: List[Product with Serializable] = List(None, List())
scala>