Reprinted from Fineqtbull http://fineqtbull.iteye.com/blog/477994
One of the JE Students came to me and asked me about the Scala type parameter covariance, inversion, type upper bound and type of the lower bound of the use of methods and principles, although I have just learned, in the main investigation of the "programing in Scala" Chapter 19, try to do a summary below. If there are errors, please correct them.
Let's talk about covariance and contravariance (in fact, non-change). Covariance and contravariance are mainly used to solve the generalization problem of parametric types. Since the parameters of the parameterized type (parameter type) are variable, when the parameters of the two parameterized types are inherited (generalized), can the parameterized type be generalized? In Java, this is not a generalization, but Scala offers three choices, covariant, contravariant, and non-variable. The following is a description of the three scenarios, first of all assume that there is a parameterized character queue, it can have the following three kinds of definitions.
1) Trait queue[t] {}
This is a non-variable situation. In this case, when the type S is a subtype of type A, then Queue[s] is not considered a subtype of queue[a] or a parent type, and this is the case with Java.
2) Trait queue[+t] {}
This is a covariant situation. In this case, when the type S is a subtype of type A, then Queue[s] can also be considered a subtype of queue[a}, i.e. Queue[s] can be generalized to queue[a]. That is, the generalization direction of the parameterized type is consistent with the direction of the parameter type, so called covariant.
3) Trait queue[-t] {}
This is the inverse condition. In this case, when the type S is a subtype of type A, then Queue[a] can, in turn, be considered a subtype of queue[s}. That is, the generalization direction of the parameterized type is opposite to the direction of the parameter type, so it is called inversion.
Then look at an example.
Package fineqtbull.customer//Publication Class Class Publication (Val title:string)//Books Class book (title:string) extends Publication (title)//Libraries class object Library {//define all books in the library val Books:set[book] = Set (new book ("Progr Amming in Scala ("Walden"))//print all the contents of the book, using externally-passed functions to implement the Def printbooklist (Info:book = ANYREF) {//Confirm that the function of one parameter in Scala is actually an instance of the Function1 feature assert (Info.isinstanceof[function1[_, _]])//Print For (books <-Books) println (info)}//Print all book contents, use an instance of an externally passed-in getinfoaction feature to achieve Def Printbokklis Tbytrait[p: Book, R <: Anyref] (action:getinfoaction[p, R]) {//Print for (book <-Book s) println (book)}}//to obtain the content characteristics of books, the type of P-type parameter is the lower bound of the type Book,r type parameter is the upper bound of the Anyreftrait getinfoaction[p;: Book, R & lt;: Anyref] {//Get text description of book content, corresponding () operator def apply (book:p): r}//Singleton object, file main program object Customer extends application {// Defines the function that gets the title of the publication Def GetTitle (p:publication): String = p.title//Use function to print library.printbooklist (getTitle)//Use instance of feature Getinfoaction to print Library.printbokk Listbytrait (new Getinfoaction[publication, string] {def apply (p:publication): string = P.title})}
The Printbooklist method of the library singleton object in the example above uses a function to obtain the contents of the book. In Scala, a function is also an object, and in this case the function has a parameter, which is actually an instance of the following feature.
Trait Function1[-s, +T] { def apply (x:s): T}
The info parameter of the printbooklist is the Function1 type, and the-s type parameter of Function1 is contravariant, and the +t parameter is covariant. The Assert (Info.isinstanceof[function1[_, _]]) Statement of the Printbooklist method can verify this. From the definition of the Printbooklist method, we know that the s type parameter of info is the book,t type parameter is anyref. In the main function, however, library.printbooklist (GetTitle) is used, and the corresponding s in the GetTitle function is Publication,t is a string. Why can it be inconsistent with the original definition of printbooklist, which is the power of covariance and contravariance. Since-S is contravariant, and publication is the parent of book, publication can replace (generalize as) book. Because +t is covariant, and string is a subclass of Anyref, string can instead (generalize to) anyref. As a result, the main program's statements are completely correct.
Next, we'll talk about the upper and lower bounds of the type, and they have the following meanings.
1) U;: T
This is the definition of the lower bound of the type, that is, you must be the parent of type T (or itself, which you can think of as your own parent).
2) S <: T
This is the definition of the upper bounds of the type, that is, s must be a subclass of type T (or itself, itself can be considered a subclass of its own).
Then use the previous example to illustrate: and <: How to use. The Printbokklistbytrait method implements the same functionality as printbooklist, but it is implemented by passing in a Feature object. That is, new Getinfoaction[publication, string] {} and Def getTitle (p:publication): string is equivalent, and getinfoaction is used in the definition;: and <: To replace the Function1 in + and-. That is because of the: so that publication can replace book, because of <: string can replace Anyref.
So why is s in Function1 the inverse and T is covariant, which is the form of the Apply method. The argument type of the Apply method is s determines that s must be contravariant, and the return type is T, which determines that T is covariant, which is also a mandatory requirement for Scala language.
Let's get back to it, so why is Scala going to have this rule? This is actually related to the Liskov substitution principle, which stipulates that the subclass of the T type is the U-type condition is that the T object can be substituted everywhere the U object appears. At the same time, for the same method definition in U and T, it is necessary to ensure that the parameter type requirements for T are relatively small, and that the return type of T is provided more. From the class of this article, the parameter type publication is the parent class of book, so the demand is less than book, and the return type string is the subclass of Anyref, which provides more than Anyref. The above is Def getTitle (p:publication): string can replace Info:book and anyref, and it is also the theoretical basis for Scala to define covariance and contravariance rules.
====================================================================
A simplified list definition is made, and the list object consists of the head (the first element) and the tail (a list of all the subsequent elements except the first element). Nil is an empty list object, because no matter what the list's generic type is, there is no difference in the meaning and behavior of the empty list, so the global only needs to exist an empty list object that is nil.
123456789101112131415 |
trait List[+T] {
def isEmpty
: Boolean
def head
: T
def tail
: List[T]
}
class Cons[T](
val head
: T,
val tail
: List[T])
extends List[T] {
def isEmpty
= false
}
object Nil
extends List[Nothing] {
def isEmpty
: Boolean
= true
def head
: Nothing
= throw new NoSuchElementException(
"Nil.head"
)
def tail
: Nothing
= throw new NoSuchElementException(
"Nil.tail"
)
}
|
This completes the definition of the list. We can define the list object in the following way.
12 |
val x : list[string] = nil val ages : list[int] = new cons ( 16 Code class= "Scala keyword" >new cons ( 22 Code class= "Scala Plain", Nil)) |
First let's take a look at the definition of nil, nil, the class that the Singleton object belongs to, inherits from List[nothing], why can I define the X of the list[string] type as this object? Because generic type T is covariant, nothing in Scala is a subclass of all other classes. So list[nothing] is the subclass of List[string], and according to the Liskov substitution principle, such a definition is legal.
Next, we want to add a prepend method like the list class to generate a new list that adds an element to the original list header:
1234 |
trait List[+T] { // omit other methods def prepend(elem : T) : List[T] = new Cons(elem, this ) } |
However, this approach is not compiled. Why? As explained in the previous blog post, covariant types cannot be used as parameter types for methods . And this kind of prepend operation seems to be very common sense, then is the Scala rule set unreasonable?
Let's recall the Liskov substitution principle. If Bird is a subclass of Animal, then List[bird] is the subclass of List[animal], then if List[animal] can prepend an instance of Animal type, List[bird] It is also possible to prepend an instance of a animal type, but it is not possible to follow the definition above, so it is wrong to violate the Liskov substitution principle.
To solve this problem, we need to introduce the concept of the nether of a generic type.
1234 |
trait list[+t] { //omit other methods def prepend[u; : t] (Elem : u) : list[u] = new cons (elem, this } |
The implication of this definition is that the Prepend method accepts a parameter of type U, and u must be the parent of T or T (";:" denotes the lower bound of a generic type, while "<:" represents the upper bound accordingly), and the return type is list[u]. Take Animal and Bird example, and then assume that chicken is a subclass of Bird, then according to this definition, List[bird] can prepend an instance of Animal, return to List[animal], Prepend a chicken instance is not disallowed, but the U type is not chicken, you must raise the U type to at least Bird, so prepend an instance of chicken, the result is return List[bird]. Such a definition satisfies the Liskov substitution principle, and all operations that can be performed on List[animal] can be performed on List[bird].
Let's take another example, assuming that both Baldeagle and Crowneagle are aeroplane subclasses, and Baldeagle and Crowneagle have no relationship. So List[baldeagle] What would happen if prepend an instance of Crowneagle? The type u cannot be crowneagle, but must be lifted upward until u is the parent of baldeagle or baldeagle, so you will be promoted to aeroplane, so the result will be return List[aeroplane].
In this way, it is easy to infer the Scala rules for the generic upper and lower bounds.
- Covariant types can be used as the lower bounds of a generic type
- Contravariant types can be used as upper bounds of a generic type
Scala Learning Notes-covariance (+), inverse (-), type upper bound (<:) and type lower bound (>:) use) in the type parameter