Co/Contravariance? Tuple<string, IEnumerable<string>> not satisfied with List<string> - ienumerable

I cant wrap my head around why I cant call a method with this signature:
public void Test(Tuple<int, IEnumerable<string>> x);
like that:
Test(Tuple.Create(4, new List<string>()))
I think this has to do with co/contravariance but thats just a guess. Can somebody share his cleverness?

Yes, the Tuple class is not marked as co- or contravariant, the reason being only interfaces can be co- or contravariant. So the type parameters have to match exactly. Here's the reason why classes can't be co- and contravariant.

Related

Difference between 2 class definitions regarding contravariant type?

class Contravariant[-T](val other:T)
error: contravariant type T occurs in covariant position in type T of value other
However this one succeeds
class MMX[-T](x:T)
What is the difference?
Thanks
As Luis Miguel Mejía Suárez said, in the first example, other is a field, and fields cannot be contravariant (as Dmytro Mitin pointed out, not fields with modifier private[this] or protected[this]), although they can be covariant. Consider this example, assuming your first example worked:
class Contravariant[-T](val other: T)
val stringList = List[Contravariant[String]](new Contravariant[Any](1))
val string: String = stringList.head.other //This can't work, because 1 is not a String
Here you can see what happens (I used #uncheckedVariance to make it work).
In the second example, x is simply a parameter to your constructor, and parameters can be contravariant, so it works.

Pattern matching with type parameter bounded to final class

Here is an example
def maybeeq[A <: String](x: A):A = x match {
case z:A => x
}
It produced the following error message during compilation
Error:(27, 12) scrutinee is incompatible with pattern type;
found : A
required: String
case z:A => x
I can put any final class into A's bound to reproduce the error.
Why this compiles for non-final classes but fails on the final? Why type erasure not just replace A with String?
Edited:
Note: such bound allows me to pass String-typed value to 'x' parameter. So 'x' can be just a String and don't have to be subtype of string, so I'm not asking compiler to compile method with incorrect signature. In the real-world code I would just put String instead on A parameter, but from the experimental perspective I'm interested why such extra restriction on top of existing restriction (based on final class nature) is needed.
TBH this is a question about compiler design which can only be answered by those who implemented such check
There is a test in compiler test suite that requires such error to be shown. It has something to do with type information being discarded to a point where a concrete type cannot be assigned to variable, but the reasons for that cannot be understood from git blame of that test.
I'll point out, however, that there is still a number of ways to satisfy A <: String without A being known at compile time to be a String. For one, Null and Nothing satisfy that, being at the bottom of Scala type hierarchy. Those two are explicitly disallowed at type matching. The other example is a bit more involved:
val UhOh: { type T <: String } = new { type T = String }
implicitly[UhOh.T <:< String] // satisfies type bound
implicitly[UhOh.T =:= String] // won't compile - compiler cannot prove the type equality
This is similar to some newtyping patterns, e.g. shapeless.tag
Out of all these possibilities, only one that can do anything reasonable is when A =:= String because String is the only type that can be actually checked at runtime. Oh, except when you use generic type in match - that does not work at all (not without ClassTag in scope at least) because such types are eliminated by erasure.
Final class cannot be extended.so for def maybeeq[A <: String](x: A) is not a correct syntax, since String is final, there should not have any subtype extend from String. the compiler smartly point out this issue.

Why can't class-wide upper-bound constraints be covariant and lower-bound constraints be contravariant in Hack?

Regardless of the variance of parameter on the left side, the constraints placed on Ta and Tb in the following declaration fail the typecheck:
class A<+TCov, -TCon, [±]Ta as TCov, [±]Tb super TCon> {
public function __construct(private Ta $ta, private Tb $tb) {}
// [various methods making use of Ta and Tb]
}
It's worth noting that the empty class declaration doesn't raise errors, but once the constrained parameters are used (in otherwise valid positions given their own variances), the typechecker raises one of the following:
Illegal use of covariant type parameter (Typing[4120])... as constraints are contravariant
Illegal use of contravariant type parameter (Typing[4121])... super constraints are covariant
with reference to the parameter on the right side of the constraint.
I can more understand why generic methods pose problems. The violating positions are fairly obvious, and using the arguments in positions matching the variance of their constraints are impossible:
class A<+TCov, -TCon> {
public function cov_violate<T as TCov>(T $v): void {
// T can be cast to TCov and violate type if the original type is a subtype of T
}
public function con_violate<T super TCon>(): T {
// vice versa for the contravariant parameter
}
public function cov_impossible<T as TCov>(): T {
// how will we produce a T-typed value?
}
public function con_impossible<T super TCov>(T $v): void {
// what will we do with a T-typed value?
}
}
But what is the problem with class-wide parameters? For all six errant relationships ({+|-| }T as +TCov and {+|-| }T super -TCon) I can't think up a scenario where these wouldn't be type-safe. In my mind, their variances seem to either restrict their casting direction or their positions sufficiently to let declaring these relationships be safe.
I was running 3.13.1 at the time of this question, but luckily the restriction has been relaxed to allow this subtyping of class type parameters as of HHVM 3.14.4 by this commit! The commit also points to this Microsoft paper for a proof of soundness.

Contravariance vs Covariance in Scala

I just learned Scala. Now I am confused about Contravariance and Covariance.
From this page, I learned something below:
Covariance
Perhaps the most obvious feature of subtyping is the ability to replace a value of a wider type with a value of a narrower type in an expression. For example, suppose I have some types Real, Integer <: Real, and some unrelated type Boolean. I can define a function is_positive :: Real -> Boolean which operates on Real values, but I can also apply this function to values of type Integer (or any other subtype of Real). This replacement of wider (ancestor) types with narrower (descendant) types is called covariance. The concept of covariance allows us to write generic code and is invaluable when reasoning about inheritance in object-oriented programming languages and polymorphism in functional languages.
However, I also saw something from somewhere else:
scala> class Animal
 defined class Animal
scala> class Dog extends Animal
 defined class Dog
scala> class Beagle extends Dog
 defined class Beagle
scala> def foo(x: List[Dog]) = x
 foo: (x: List[Dog])List[Dog] // Given a List[Dog], just returns it
scala> val an: List[Animal] = foo(List(new Beagle))
 an: List[Animal] = List(Beagle#284a6c0)
Parameter x of foo is contravariant; it expects an argument of type List[Dog], but we give it a List[Beagle], and that's okay
[What I think is the second example should also prove Covariance. Because from the first example, I learned that "apply this function to values of type Integer (or any other subtype of Real)". So correspondingly, here we apply this function to values of type List[Beagle](or any other subtype of List[Dog]). But to my surprise, the second example proves Cotravariance]
I think two are talking the same thing, but one proves Covariance and the other Contravariance. I also saw this question from SO. However I am still confused. Did I miss something or one of the examples is wrong?
A Good recent article (August 2016) on that topic is "Cheat Codes for Contravariance and Covariance" by Matt Handler.
It starts from the general concept as presented in "Covariance and Contravariance of Hosts and Visitors" and diagram from Andre Tyukin and anoopelias's answer.
And its concludes with:
Here is how to determine if your type ParametricType[T] can/cannot be covariant/contravariant:
A type can be covariant when it does not call methods on the type that it is generic over.
If the type needs to call methods on generic objects that are passed into it, it cannot be covariant.
Archetypal examples:
Seq[+A], Option[+A], Future[+T]
A type can be contravariant when it does call methods on the type that it is generic over.
If the type needs to return values of the type it is generic over, it cannot be contravariant.
Archetypal examples:
`Function1[-T1, +R]`, `CanBuildFrom[-From, -Elem, +To]`, `OutputChannel[-Msg]`
Regarding contravariance,
Functions are the best example of contravariance
(note that they’re only contravariant on their arguments, and they’re actually covariant on their result).
For example:
class Dachshund(
name: String,
likesFrisbees: Boolean,
val weinerness: Double
) extends Dog(name, likesFrisbees)
def soundCuteness(animal: Animal): Double =
-4.0/animal.sound.length
def weinerosity(dachshund: Dachshund): Double =
dachshund.weinerness * 100.0
def isDogCuteEnough(dog: Dog, f: Dog => Double): Boolean =
f(dog) >= 0.5
Should we be able to pass weinerosity as an argument to isDogCuteEnough? The answer is no, because the function isDogCuteEnough only guarantees that it can pass, at most specific, a Dog to the function f.
When the function f expects something more specific than what isDogCuteEnough can provide, it could attempt to call a method that some Dogs don’t have (like .weinerness on a Greyhound, which is insane).
That you can pass a List[Beagle] to a function expecting a List[Dog] is nothing to do with contravariance of functions, it is still because List is covariant and that List[Beagle] is a List[Dog].
Instead lets say you had a function:
def countDogsLegs(dogs: List[Dog], legCountFunction: Dog => Int): Int
This function counts all the legs in a list of dogs. It takes a function that accepts a dog and returns an int representing how many legs this dog has.
Furthermore lets say we have a function:
def countLegsOfAnyAnimal(a: Animal): Int
that can count the legs of any animal. We can pass our countLegsOfAnyAnimal function to our countDogsLegs function as the function argument, this is because if this thing can count the legs of any animal, it can count legs of dogs, because dogs are animals, this is because functions are contravariant.
If you look at the definition of Function1 (functions of one parameter), it is
trait Function1[-A, +B]
That is that they are contravariant on their input and covariant on their output. So Function1[Animal,Int] <: Function1[Dog,Int] since Dog <: Animal
Variance is used to indicate subtyping in terms of Containers(eg: List). In most of the languages, if a function requests object of Class Animal, passing any class that inherits Animal(eg: Dog) would be valid. However, in terms of Containers, these need not be valid.
If your function wants Container[A], what are the possible values that can be passed to it? If B extends A and passing Container[B] is valid, then it is Covariant(eg: List[+T]). If, A extends B(the inverse case) and passing Container[B] for Container[A] is valid, then it is Contravariant. Else, it is invariant(which is the default). You could refer to an article where I have tried explaining variances in Scala
https://blog.lakshmirajagopalan.com/posts/variance-in-scala/

Covariance and method parameters

I have a confusing problem in my project and can't quite solve it, so please help me!
Here is a sample code that simplifies my original one:
trait Sample[A] {
def doit(param: A)
}
case object SampleEx1 extends Sample[Int] {
def doit(param: Int) = {
param + 0
}
}
Now I need to make A covariance for outer reasons, but it results in an error as commented out:
trait Sample[+A] {
def doit(param: A) // ERR: covariant type A occurs in contravariant position in type A of value param
}
case object SampleEx1 extends Sample[Int] {
def doit(param: Int) = {
param + 0
}
}
So I stacoverflowed and found a solution with another type B, but then another error happens:
trait Sample[+A] {
def doit[B >: A](param: B)
}
case object SampleEx1 extends Sample[Int] {
def doit[Int](param: Int) = {
param + 0 // type mismatch; found : Int(0) required: String
}
}
Apparently param is no longer Int because of [B >: Int].
I tried solving this one with myself and with google but couldn't get it. Could anyone help? Thank you so much! :))
The first error covariant type A occurs in contravariant position in type A of value param means that if a generic type Foo declares itself to be covariant over T (i.e. Foo[+T]), it means that its methods can only return T and not require it. Otherwise type consistency will be violated. For instance you could pass in an instance of Sample[Dog] where Sample[Animal] is required, and then something could call doit(new Duck) on it, even though Sample[Dog]#doit can only handle instances of Dog. However, return values behave the exact opposite way in this context (I'll let you figure out why).
However this
def doit[Int](param: Int)
means doit has a type parameter called Int, which has nothing to do with the Int type (although it sure does seem like it does at first impression, which is why you should never use type parameter names that coincide with the names of other/built-in types). So the error you're getting is because Int in that context means "any type", and using + on any type will fall back to string concatenation as opposed to arithmetic addition.
instead you need (to correctly inherit from Sample[+A]):
def doit[B >: Int](param: B)
however, that will still not allow you to do addition on param because param is now any supertype of Int, not Int itself or a subtype thereof.
So I do not see how you can "fix" this—the way variance works fundamentally simply doesn't allow for generic types to be covariant over method parameters. This has nothing to do with Scala really. But see e.g. http://blogs.atlassian.com/2013/01/covariance-and-contravariance-in-scala/ or http://docs.scala-lang.org/tutorials/tour/variances.html for more information on how variance works and why it has to work exactly like it does (in any language implementing correct variance rules).
I think a better way in general to get a really helpful answer on Stackoverflow is to also describe what you really need to achieve, not just the implementation you've been working on so far.