Directory of covariance, inversion, upper bound, nether, etc. in Scala[−]
- Covariance and Contravariance in Java
- Scala's co-transformation
- Scala's Contravariance
- Nether Lower Bounds
- Upper bound Upper Bounds
- Integrated covariance, inversion, upper bound, lower bound
- View Bound <%
- Context Bound
- Reference documents
The covariant contravariance in Scala is not the same as covariant contravariance in Java, and looks more complex. This article summarizes these concepts in Scala.
First look at several concepts:
- covariant covariant. Enables you to use subclasses of a type other than the original specified
- contravariance inversion. Enables you to use a parent class that is more than the original specified type.
- invariance not change. You can only use the original specified type, not covariant and contravariant
- Upper bounds Upper bound.
- Lower bounds Nether.
Covariance and Contravariance in Java
First, let's review the covariance and contravariance in Java so that we can easily understand the covariance and contravariance in Scala.
1 co-change
123456 |
Class Super {Object getsomething () {}}class Sub extends Super {String getsomething () {}} |
Sub.getsomething () is a covariant type because its return type is a subclass of the super.getsomething return type.
2 Inversion
123456 |
Class super{ void dosomething (String parameter)}class Sub extends super{ void dosomething (Object parameter) } |
Sub.getsomething () is a contravariant type because its input parameter is the parent class of the super.getsomething input parameter.
3 generic type
Covariance and contravariance are also found in generics.
12345678910 |
List<string> alist ... list<? Extends object> covariantlist = alist; list<? Super string> contravariantlist = alist; Covariantlist.add ("D"); //wrong Object a = Covariantlist.get (0); contravariantlist.add ("D"); //ok String B = Contravariantlist.get (1); //wrong Object C = Contravariantlist.get (2); |
You can call covariantList
all methods that do not require a generic parameter, because the generic parameter must extends Object, but at compile time you do not know its exact type. However, you can call the Getter method because the return type always conforms to the object type.
contravariantList
On the contrary, you can call all methods with generic parameters, because you can explicitly pass in a string's parent class. But the getter method does not work.
Scala's co-transformation
The first thing we need to know is the subtype (subtyping). A class can be a subclass of another class (sub-) or a parent class (super-). We can use the mathematical concept (partial order) to define:
When we define a covariant type List[A+]
, List[child] can be a subtype of list[parent].
When we define an contravariant type List[-A]
, List[child] can be the parent type of list[parent].
Look at the following example:
12345678910 |
class animal {} class bird extends animal {} class consumer[t] }class test extends app { val c:consumer[bird] = new consumer[bird] (new Bird) val c2:consumer[animal] = c } |
c
cannot be assigned to c2
because it Consumer
is defined as an immutable type.
Change it a little bit:
12345678910 |
class animal {} class bird extends animal {} class consumer[+t] }class test extends app { val c:consumer[bird] = new consumer[bird] (new Bird) val c2:consumer[animal] = c } |
Because Consumer
it is defined as a covariant type, Consumer[Bird]
it is a Consumer[Animal]
subtype, so it can be assigned a value c2
.
Scala's Contravariance
Change the example above:
12345678910 |
class animal {} class bird extends animal {} class consumer[-t] }class test extends app { val c:consumer[bird] = new consumer[bird] (new Bird) val c2:consumer[hummingbird] = c } |
This is Consumer[-T]
defined as an inverse type, so it can be assigned Consumer[Bird]
Consumer[Hummingbird]
to a sub-type c
c2
.
Nether Lower Bounds
If the covariant class contains a method with a type parameter:
123456 |
class Animal {} class Bird extends Animal {} class consumer[+T](t:t) { def use (t:t) = {}} |
The compilation will be faulted. The error message is "covariant type T occurs in contravariant position in type T of Value T".
However, there is no problem if you return the result as a type parameter.
123456 |
class Animal {} class Bird extends Animal {} class consumer[+t](t:t) { def get (): t = {new T}} |
In order to use type parameters in the parameters of a method, you need to define the nether:
123456 |
class Animal {} class Bird extends Animal {} class consumer[+t](t:t) {def use[u;: t] (u:u) = {println (U)}} |
Upper bound Upper Bounds
Take a look at an example of an upper bound used in an inverter class:
123456 |
class Animal {} class Bird extends Animal {} class consumer[-T](t:t) {def get[u <: t] (): U = {new u} } |
To see where the return value of the method is covariant, the parameter of the method is the position of the inverse.
Therefore, the type parameter of the covariant class can be used in the type of the return value of the method, and must be bound using the nether binding on the method's parameter type >:
.
The type parameter of the Contravariant class can be used on the parameter type of the method, and must be bound with the upper bound when used as the return value type of the method <:
.
Integrated covariance, inversion, upper bound, lower bound
A comprehensive example:
1234567891011121314 |
class Animal {} class Bird extends Animal {} class consumer[-s,+T]() { def M1[u;: t] (u:u): t = {new T} //covariant, Nether def m2[u <: S] (s:s): U = {new u} //Invert, upper bound }class Test extends App { val C:consumer[animal,bird] = new Consumer[animal,bird] () val C2:consumer[bird,animal] = cC2.M1 (new Animal)C2.M2 (new Bird)} |
View Bound
<%
Scala also has a view binding feature, such as
123456 |
class Bird {def sing = {}} class Toy {} class consumer[T <% Bird]() {def use (t:t) = T.sing } |
or the type parameter on the method:
1234567891011 |
class Bird {def sing = {}} class Toy {} class Consumer() {def use[t <% Bird] (t:t) = t.sing} class Test extends App { val c = new Consumer ()c.use (new Toy)} |
It requires that T must have an implicit conversion to convert to bird, T => Bird
that is, otherwise the above code compiles an error:
1 |
No implicit view available from Toy = Bird. |
Add an implicit conversion, compile through.
1234567891011121314 |
import Scala.language.implicitConversions class bird {def sing = {}} class toy {} class consumer () { def use[t <% Bird] (t:t) = t.sing } < span class= "keyword" >class test extends app { implicit def toy2bird (T:toy) = new Bird val c = new Consumer () c.use (new Toy) } |
Context Bound
The context bound is introduced in Scala 2.8.0 and is also known as type class pattern.
The view bound A <% String
is used, and the context bound requires a parameterized type, such as Ordered[A]
.
It declares a type A
, implicitly having a type B[A]
, with the following syntax:
1 |
def F[a:b] (a:a) = g (A) //Where g requires an implicit value of type B[a] |
A clearer example:
1 |
def F[a:classmanifest] (n:int) = new Array[a] (n) |
Another example
1 |
def F[a:ordering] (a:a, b:a) = Implicitly[ordering[a]].compare (A, B) |
Covariance, inversion, upper bounds, nether, etc. in Scala