case class Level[B](b: B){
def printCovariant[A<:B](a: A): Unit = println(a)
def printInvariant(b: B): Unit = println(b)
def printContravariant[C>:B](c: C): Unit = println(c)
}
class First
class Second extends First
class Third extends Second
//First >: Second >: Third
object Test extends App {
val second = Level(new Second) //set B as Second
//second.printCovariant(new First) //error and reasonable
second.printCovariant(new Second)
second.printCovariant(new Third)
//second.printInvariant(new First) //error and reasonable
second.printInvariant(new Second)
second.printInvariant(new Third) //why no error?
second.printContravariant(new First)
second.printContravariant(new Second)
second.printContravariant(new Third) //why no error?
}
It seems scala's lowerbound type checking has bugs... for invariant case and contravariant case.
I wonder above code are have bugs or not.
Always keep in mind that if Third extends Second then whenever a Second is wanted, a Third can be provided. This is called subtype polymorhpism.
Having that in mind, it's natural that second.printInvariant(new Third) compiles. You provided a Third which is a subtype of Second, so it checks out. It's like providing an Apple to a method which takes a Fruit.
This means that your method
def printCovariant[A<:B](a: A): Unit = println(a)
can be written as:
def printCovariant(a: B): Unit = println(a)
without losing any information. Due to subtype polymorphism, the second one accepts B and all its subclasses, which is the same as the first one.
Same goes for your second error case - it's another case of subtype polymorphism. You can pass the new Third because Third is actually a Second (note that I'm using the "is-a" relationship between subclass and superclass taken from object-oriented notation).
In case you're wondering why do we even need upper bounds (isn't subtype polymorphism enough?), observe this example:
def foo1[A <: AnyRef](xs: A) = xs
def foo2(xs: AnyRef) = xs
val res1 = foo1("something") // res1 is a String
val res2 = foo2("something") // res2 is an Anyref
Now we do observe the difference. Even though subtype polymorphism will allow us to pass in a String in both cases, only method foo1 can reference the type of its argument (in our case a String). Method foo2 will happily take a String, but will not really know that it's a String. So, upper bounds can come in handy when you want to preserve the type (in your case you just print out the value so you don't really care about the type - all types have a toString method).
EDIT:
(extra details, you may already know this but I'll put it for completeness)
There are more uses of upper bounds then what I described here, but when parameterizing a method this is the most common scenario. When parameterizing a class, then you can use upper bounds to describe covariance and lower bounds to describe contravariance. For example,
class SomeClass[U] {
def someMethod(foo: Foo[_ <: U]) = ???
}
says that parameter foo of method someMethod is covariant in its type. How's that? Well, normally (that is, without tweaking variance), subtype polymorphism wouldn't allow us to pass a Foo parameterized with a subtype of its type parameter. If T <: U, that doesn't mean that Foo[T] <: Foo[U]. We say that Foo is invariant in its type. But we just tweaked the method to accept Foo parameterized with U or any of its subtypes. Now that is effectively covariance. So, as long as someMethod is concerned - if some type T is a subtype of U, then Foo[T] is a subtype of Foo[U]. Great, we achieved covariance. But note that I said "as long as someMethod is concerned". Foo is covariant in its type in this method, but in others it may be invariant or contravariant.
This kind of variance declaration is called use-site variance because we declare the variance of a type at the point of its usage (here it's used as a method parameter type of someMethod). This is the only kind of variance declaration in, say, Java. When using use-site variance, you have watch out for the get-put principle (google it). Basically this principle says that we can only get stuff from covariant classes (we can't put) and vice versa for contravariant classes (we can put but can't get). In our case, we can demonstrate it like this:
class Foo[T] { def put(t: T): Unit = println("I put some T") }
def someMethod(foo: Foo[_ <: String]) = foo.put("asd") // won't compile
def someMethod2(foo: Foo[_ >: String]) = foo.put("asd")
More generally, we can only use covariant types as return types and contravariant types as parameter types.
Now, use-site declaration is nice, but in Scala it's much more common to take advantage of declaration-site variance (something Java doesn't have). This means that we would describe the variance of Foo's generic type at the point of defining Foo. We would simply say class Foo[+T]. Now we don't need to use bounds when writing methods that work with Foo; we proclaimed Foo to be permanently covariant in its type, in every use case and every scenario.
For more details about variance in Scala feel free to check out my blog post on this topic.
Related
Consider covariant type parameter A
case class Foo[+A](a: A):
def bar(a: A) = a // error: covariant type A occurs in contravariant position
def zar(f: A => Int) = f(a) // ok
|
This is contravariant position. Why is it ok?
Foo(41).zar(_ + 1) // : Int = 42
Why is it accepted as argument to zar when it occurs in contravariant position in A => Int?
Following the idea by #sarveshseri I will provide an intuitive explanation rather than a formal one. Both because I do not know a the details of a formal one and because I hope this one would be more helpful for readers.
First some disclaimers:
I may have typos or some errors, please edit the answer if you notice it.
As mentioned above the mental model I am to describe is an approximation, what really happens at compile time and at runtime will be different.
During this answer I will be referring to types and classes interchangeable. This is WRONG and I myself have pointed this on other answers. In this case it is for simplicity of this mental model; but I recommend that after variance clicks then you mix the distinction of types and classes to that model: https://typelevel.org/blog/2017/02/13/more-types-than-classes.html
Connected to the previous point, I will be using concrete/simple types on my example. Thankfully, thanks to type erasure and parametricity what I will explain applies to type constructors and other complex types.
I will also be using methods and functions interchangeably, this is also WRONG: https://docs.scala-lang.org/tutorials/FAQ/index.html#whats-the-difference-between-methods-and-functions
Now let's get to it. First let's assume that if a method accepts a Foo then it can only accept values of type Foo and nothing else, period.
Then let's assume that subtyping is actually an "implicit" function that "cast" values. So B <: A is just B => A
And let's imagine that such "cast" is a disguise, the value is actually the same one but seen differently (this is basically the Liskov principle).
So, when you try to pass a B to a method that expects an A then this implicit cast will be inserted by the compiler. That way in runtime, that method receives a value that is seen like one of type A and not one of type B; but the value is still of type B (actually here we would be talking about classes not types, but I hope you get the idea).
With that then let's see what happens to bar and baz methods of your covariant class Foo
Since Foo[Dog] <: Foo[Animal] the I can cast the former as the latter, then given Cat <: Animal I can also cast the former to the latter.
Finally, I could pass this Cat disguised as a Animal to the bar method of Foo[Dog] disguised as a Foo[Animal] but then at runtime we would be passing a Cat to a method that expects a Dog kataplum! Unless, of course, such method was always prepared for such situation.
That is why bar has to be defined like [B >: A](b: B). Here we are saying that we can accept any B for which the compiler can produce the implicit cast function A => B (the opposite as before, thanks to Any such type and such function is always possible). And then the implementation of bar should be able to work for that new type B and using the cast function when is required.
Which would mean that the previous example doesn't blow up at runtime, since we could have just passed the Cat directly without going via indirect disguises; this works because the compiler is always able to infer that B should be Animal (the LUB of Cat and Dog) so it would cast the Cat as Animal and would pass that to bar as well as the cast function Dog => Animal
Note, the presence of the A => B cast function implies that the compiler can also create the F[A] => F[B] function, if F is covariant.
Now let's see what happens to baz.
Again, we would cast a Foo[Dog] as a Foo[Animal] and then we would try to call baz with a function Animal => Int which should work at runtime because we do not even need the disguise at all, we could have passed such a function directly to Foo[Dog] because (Animal => Int) <: (Dog => Int) this because functions are contravariant on their inputs.
But how would this work at all? Simple, the intuition says that if I am able to process / consume / receive / use any Animal then I should be able to process any Dog since those are Animals, right? Let's see it how that would work with our mental model.
I have baz(Dog => Int) and I have f(Animal => Int) what the compiler can do is create a new function g(Dog => Int) = cast(Dog => Animal) andThen f(Animal => Int) and use g instead.
Hope this helps, feel free to leave any question.
Your class can be viewed as
trait Function1[-Input, +Result] // Renamed the type parameters for clarity
case class Foo[+A](a: A) {
val bar: Function1[A, A] = identity
val zar: Function1[Function1[A, Int], Int] = { f => f(a) }
}
The approach taken by the compiler is to assign positive, negative, and neutral annotations at each type position in the signature; I'll notate the positions by putting + and - after the type in that position
First the top-level vals are positive (i.e. covariant):
case class Foo[+A](a: A) {
val bar: Function1[A, A]+
val zar: Function1[Function1[A, Int], Int]+
}
bar can be anything that's a subtype of Function1[A, A] and zar can be anything that's a subtype of Function1[Function1[A, Int], Int], so this makes sense (LSP, etc.).
The compiler then moves into the type arguments.
case class Foo[+A](a: A) {
val bar: Function1[A-, A+]
val zar: Function1[Function1[A, Int]-, Int+]
}
Since Input is contravariant, this "flips" the classification (+ -> -, - -> +, neutral unchanged) relative to its surrounding classification. Result being covariant does not flip the classification (if there was an invariant parameter in Function1, that would force the classification to neutral). Applying this a second time
case class Foo[+A](a: A) {
val bar: Function1[A-, A+]
val zar: Function1[Function1[A+, Int-], Int+]
}
A type parameter for the class being defined can only be used in + position if it's covariant, - position if contravariant, and anywhere if it's invariant (Int, which is not a type parameter can be considered invariant for the purpose of this analysis: i.e. we could have dispensed with annotating once we got to a type which neither was a type parameter nor had a type parameter). In bar, we have a conflict (the A-), but in zar the A+ means no conflict.
The produce/consume relationship presented in Luis's answer is a good intuitive summary. This is a partial (methods with type parameters complicate this, though I'm not totally sure how my transformation would work there...) exploration of how the compiler actually concludes that the A in zar is in a covariant position; Programming in Scala (Odersky, Spoon, Venners) describes it in more detail (in the 3rd edition, it's in section 19.4).
In Scala's upper bound concept, the given type or its super type can be passed. For example, in the below method S is the type and A is the parameter we pass. This method accepts all the values present in Scala's type system actually. S, S subtype and its super type. This is due to the fact that all types extends Any type.
def method[A >: S](a:A) = { ... }
Then why can't we write all the upper bound notations as Any (which is the universal type in Scala). The above definition can be re-written as:
def met(a:Any) = { ... }
This is easy to understand.
What sort of advantage the upperbound brings in ?
Thanks!
It allows you to lose less type information than you would by going to Any.
For example If you have Dog and Cat inherit Animal, this works:
val maybeDog: Option[Dog] = ???
val pet = maybeDog.getOrElse(Cat())
Because getOrElse has a signature of def getOrElse[B >: A](default: => B): B, it is inferred that B is Animal (as the least upper bound of Cat and Dog), so the static type of val pet is Animal. If it was using Any, the result would require unsafe casting to work further. If it was not introducing a new type parameter altogether, you would be forced to write (maybeDog: Option[Animal]).getOrElse(Cat()) to achieve the same unification.
Additionally, it restricts the implementation to not do something completely silly. For example, this typechecks:
def getOrElse[A](option: Option[A])(default: => Any): Any = 42 // Int is Any, so why not?
While this doesn't:
def getOrElse[A, B >: A](option: Option[A])(default: => B): B = 42
Because while anything can go as B, that doesn't imply that Int is always a subtype of B.
This question already has answers here:
Contravariance vs Covariance in Scala
(3 answers)
Closed 2 years ago.
I have a confusion in understanding covariance type being restricted in method parameters. I read through many materials and I am unable to get them the below concept.
class SomeThing[+T] {
def method(a:T) = {...} <-- produces error
}
In the above piece of code, a is of type T. Why can we not pass subtypes of T? All the expectations of method on parameter x, can be fulfilled by subtype of T perfectly.
Similarly when we have contravariant type T (-T), it can not be passed as method argument; but it is allowed. Why I think it can not be passed is: for e.g, say method invokes a method (present in object a)
on a which is present in T. When we pass super type of T, it may NOT be present. But it is allowed by compiler. This confuses me.
class SomeThing[-T] {
def method(a:T) = {...} <-- allowed
}
So by looking at the above, it is covariant that should be allowed in method arguments as well as in the return type. Contravariant can not be applied.
Can someone please help me to understand.
The key thing about variance is that it affects how the class looks from the outside.
Covariance says that an instance of SomeThing[Int] can be treated as an instance of SomeThing[AnyVal] because AnyVal is a superclass of Int.
In this case your method
def method(a: Int)
would become
def method(a: AnyVal)
This is clearly a problem because you can now pass a Double to a method of SomeThing[Int] that should only accept Int values. Remember that the actual object does not change, only the way that it is perceived by the type system.
Contravariance says that SomeThing[AnyVal] can be treated as SomeThing[Int] so
def method(a: AnyVal)
becomes
def method(a: Int)
This is OK because you can always pass an Int where AnyVal is required.
If you follow through the logic for return types you will see that it works the other way round. It is OK to return covariant types because they can always be treated as being of the superclass type. You can't return contravariant types because the return type might be a subtype of the actual type, which cannot be guaranteed.
I think you are attacking the problem backwards. The fact that you can't have a:T as an argument of a method if T is covariant comes as a constraint because otherwise some illogical code would be completely valid
class A
class B extends A
class C extends B
val myBThing = new SomeThing[B]
Here, myBThing.method accepts a B, and you are right that we can pass it anything that extends B, so myBThing.method(new C) is completely fine. However, myBThing.method(new A) isn't!
Now, since we've defined SomeThing with a covariant, I can also write this
val myAThing: SomeThing[A] = myBThing // Valid since B <: A entails SomeThing[B] <: Something[A] by definition of covariance
myAThing.method(new A) // What? You're managing to send an A to a method that was implemented to receives B and subtypes!
You can see now why we impose the constraint of not passing T as a parameter (parameters are in a "contravariant position").
We can make a similar argument for contravariance in the return position. Remember that contravariance means B <: A entails ``SomeThing[A] <: Something[B]`.
Assume you're defining the following
class A
class B extends A
class SomeThingA[-T](val value: T) // Compiler won't like T in a return type like myThing.value
// If the class definition compiled, we could write
val myThingA: SomeThing[A] = new SomeThing(new A)
val someA: A = myThingA.value
val myThingB: SomeThing[B] = myThingA // Valid because T contravariant
val someB: B = myThingB.value // What? I only ever stored an A!
For more details, see this answer.
In the case of class SomeThing[T], placing a + or - before the T actually effects the class itself more than the type parameter.
Consider the following:
val instanceA = new SomeThing[A]
val instanceB = new SomeThing[B]
If SomeThing is invariant on T (no + or -) then the instances will have no variance relationship.
If SomeThing is covariant on T ([+T]) then the instances will have the same variance relationship as A and B have. In other words, if A is a sub-type of B (or vice versa) then the instances will reflect that same relationship.
If SomeThing is contravariant on T ([-T]) then the instances will have the opposite variance relationship as A and B have. In other words, if A is a sub-type of B then instanceB will be a sub-type of instanceA.
But the variance indicator does effect how the type parameter can be used. If T is marked + then it can't be placed in a contravariant position and, likewise, if marked - then it can't be placed in a covariant position. We bump up against this most often when defining methods.
Scala methods are very closely related to the Scala function traits: Function0, Function1, Function2, etc.
Consider the definition of Function1:
trait Function1[-T1, +R] extends AnyRef
Now let's say you want to pass a function of this type around.
def useThisFunc(f: A => B):Unit = {...}
Because a Function1 is contravariant on its received parameter and covariant on its result, all of the following are acceptable as a useThisFunc() parameter.
val a2b : A => B = ???
val supa2b : SuperOfA => B = ???
val a2subb : A => SubOfB = ???
val supa2subb : SuperOfA => SubOfB = ???
So, in conclusion, if SomeThing is covariant on T then you can't have T as a passed parameter of a member method because FunctionX is contravariant on its parameter types. Likewise, if SomeThing is contravariant on T the you can't have T as member method return type because FunctionX is covariant on its return type.
I have following class:
case class Box[+A](value: A) {
def set(a: A): Box[A] = Box(a)
}
And the compiler complain:
Error:(4, 11) covariant type A occurs in contravariant position in type A of value a
def set(a: A): Box[A] = Box(a)
I was searching a lot about the error, but could not find something useful that
help me to understand the error.
Could someone please explain, why the error occurs?
The error message is actually very clear once you understand it. Let's get there together.
You are declaring class Box as covariant in its type parameter A. This means that for any type X extending A (i.e. X <: A), Box[X] can be seen as a Box[A].
To give a clear example, let's consider the Animal type:
sealed abstract class Animal
case class Cat extends Animal
case class Dog extends Animal
If you define Dog <: Animal and Cat <: Animal, then both Box[Dog] and Box[Cat] can be seen as Box[Animal] and you can e.g. create a single collection containing both types and preserve the Box[Animal] type.
Although this property can be very handy in some cases, it also imposes constraints on the operations you can make available on Box. This is why the compiler doesn't allow you to define def set.
If you allow defining
def set(a:A): Unit
then the following code is valid:
val catBox = new Box[Cat]
val animalBox: Box[Animal] = catBox // valid because `Cat <: Animal`
val dog = new Dog
animalBox.set(dog) // This is non-sensical!
The last line is obviously a problem because catBox will now contain a Dog! The arguments of a method appear in what is called "contravariant position", which is the opposite of covariance. Indeed, if you define Box[-A], then Cat <: Animal implies Box[Cat] >: Box[Animal] (Box[Cat] is a supertype of Box[Animal]). For our example, this is of course non-sensical.
One solution to your problem is to make the Box class immutable (i.e. to not provide any way to change the content of a Box), and instead use the apply method defined in your case class companion to create new boxes. If you need to, you can also define set locally and not expose it anywhere outside Box by declaring it as private[this]. The compiler will allow this because the private[this] guarantees that the last line of our faulty example will not compile since the set method is completely invisible outside of a specific instance of Box.
If for some reason you do not want to create new instances using the apply method, you can also define set as follows.
def set[B >: A](b: B): Box[B] = Box(b)
Others have already given an answer why the code doesn't compile, but they haven't given a solution on how to make the code compile:
> case class Box[+A](v: A) { def set[B >: A](a: B) = Box(a) }
defined class Box
> trait Animal; case class Cat() extends Animal
defined trait Animal
defined class Cat
> Box(Cat()).set(new Animal{})
res4: Box[Animal] = Box($anon$1#6588b715)
> Box[Cat](Cat()).set[Animal](new Animal{})
res5: Box[Animal] = Box($anon$1#1c30cb85)
The type argument B >: A is a lower bound that tells the compiler to infer a supertype if necessary. As one can see in the example, Animal is inferred when Cat is given.
Try to understand what it means for your Box[+A] to be covariant in A:
It means that a Box[Dog] should also be a Box[Animal], so any instance of Box[Dog] should have all the methods a Box[Animal] has.
In particular, a Box[Dog] should have a method
set(a: Animal): Box[Animal]
However, it only has a method
set(a: Dog): Box[Dog]
Now, you'd think you can infer the first one from the second, but that's not the case: I do you want to box a Cat using only the second signature? That's not doable, and that's what the compiler tells you: a parameter in a method is a contravariant position (you can only put contravariant (or invariant) type parameters).
in addition to the other answers i'd like to provide another approach:
def set[B >: A](x: B): Box[B] = Box(x)
Basically you cant put As in if A is covariant, you can only take it out (ex: returning A). If you wish to put As in, then you would need to make it contravariant.
case class Box[-A](value: A)
Do you want to do both, then just make it invariant
case class Box[A](value: A)
The best is to keep it covariant and get rid of the setter and go for an immutable approach.
I'm pretty sure I'm missing something here, since I'm pretty new to Shapeless and I'm learning, but when is the Aux technique actually required? I see that it is used to expose a type statement by raising it up into the signature of another "companion" type definition.
trait F[A] { type R; def value: R }
object F { type Aux[A,RR] = F[A] { type R = RR } }
but isn't this nearly equivalent to just putting R in the type signature of F ?
trait F[A,R] { def value: R }
implicit def fint = new F[Int,Long] { val value = 1L }
implicit def ffloat = new F[Float,Double] { val value = 2.0D }
def f[T,R](t:T)(implicit f: F[T,R]): R = f.value
f(100) // res4: Long = 1L
f(100.0f) // res5: Double = 2.0
I see that path-dependent type would bring benefits if one could use them in argument lists, but we know we can't do
def g[T](t:T)(implicit f: F[T], r: Blah[f.R]) ...
thus, we are still forced to put an additional type parameter in the signature of g. By using the Aux technique, we are also required to spend additional time writing the companion object. From a usage standpoint, it would look to a naive user like me that there is no benefit in using path-dependent types at all.
There is only one case I can think of, that is, for a given type-level computation more than one type-level result is returned, and you may want to use only one of them.
I guess it all boils down to me overlooking something in my simple example.
There are two separate questions here:
Why does Shapeless use type members instead of type parameters in some cases in some type classes?
Why does Shapeless include Aux type aliases in the companion objects of these type classes?
I'll start with the second question because the answer is more straightforward: the Aux type aliases are entirely a syntactic convenience. You don't ever have to use them. For example, suppose we want to write a method that will only compile when called with two hlists that have the same length:
import shapeless._, ops.hlist.Length
def sameLength[A <: HList, B <: HList, N <: Nat](a: A, b: B)(implicit
al: Length.Aux[A, N],
bl: Length.Aux[B, N]
) = ()
The Length type class has one type parameter (for the HList type) and one type member (for the Nat). The Length.Aux syntax makes it relatively easy to refer to the Nat type member in the implicit parameter list, but it's just a convenience—the following is exactly equivalent:
def sameLength[A <: HList, B <: HList, N <: Nat](a: A, b: B)(implicit
al: Length[A] { type Out = N },
bl: Length[B] { type Out = N }
) = ()
The Aux version has a couple of advantages over writing out the type refinements in this way: it's less noisy, and it doesn't require us to remember the name of the type member. These are purely ergonomic issues, though—the Aux aliases make our code a little easier to read and write, but they don't change what we can or can't do with the code in any meaningful way.
The answer to the first question is a little more complex. In many cases, including my sameLength, there's no advantage to Out being a type member instead of a type parameter. Because Scala doesn't allow multiple implicit parameter sections, we need N to be a type parameter for our method if we want to verify that the two Length instances have the same Out type. At that point, the Out on Length might as well be a type parameter (at least from our perspective as the authors of sameLength).
In other cases, though, we can take advantage of the fact that Shapeless sometimes (I'll talk about specifically where in a moment) uses type members instead of type parameters. For example, suppose we want to write a method that will return a function that will convert a specified case class type into an HList:
def converter[A](implicit gen: Generic[A]): A => gen.Repr = a => gen.to(a)
Now we can use it like this:
case class Foo(i: Int, s: String)
val fooToHList = converter[Foo]
And we'll get a nice Foo => Int :: String :: HNil. If Generic's Repr were a type parameter instead of a type member, we'd have to write something like this instead:
// Doesn't compile
def converter[A, R](implicit gen: Generic[A, R]): A => R = a => gen.to(a)
Scala doesn't support partial application of type parameters, so every time we call this (hypothetical) method we'd have to specify both type parameters since we want to specify A:
val fooToHList = converter[Foo, Int :: String :: HNil]
This makes it basically worthless, since the whole point was to let the generic machinery figure out the representation.
In general, whenever a type is uniquely determined by a type class's other parameters, Shapeless will make it a type member instead of a type parameter. Every case class has a single generic representation, so Generic has one type parameter (for the case class type) and one type member (for the representation type); every HList has a single length, so Length has one type parameter and one type member, etc.
Making uniquely-determined types type members instead of type parameters means that if we want to use them only as path-dependent types (as in the first converter above), we can, but if we want to use them as if they were type parameters, we can always either write out the type refinement (or the syntactically nicer Aux version). If Shapeless made these types type parameters from the beginning, it wouldn't be possible to go in the opposite direction.
As a side note, this relationship between a type class's type "parameters" (I'm using quotation marks since they may not be parameters in the literal Scala sense) is called a "functional dependency" in languages like Haskell, but you shouldn't feel like you need to understand anything about functional dependencies in Haskell to get what's going on in Shapeless.