The version that Scala uses in this article is 2.11.8 17th, type parameter 17.1 generics
generic class
Class Pair[t, S] (Val first:t, Val second:s)
//instantiation
val p = new Pair ("String")
val p2 = new Pair[any, any ] ("String")
Generic Functions
def Getmiddle[t] (a:array[t]) = A (A.LENGTH/2)
//Call
Getmiddle (Array ("Mary", "had", "a", "little", "lamb"))
The following specific function is saved to f
val f = getmiddle[string] _
17.2 Definition
type variable definition
Upper bound (<:)
Class Pair[t <: Comparable[t]] (Val first:t, Val second:t) {
def smaller = if (First.compareto (second) < 0) fi RST Else second
}
Nether (;:)
def Replacefirst[r;: T] (newfirst:r) = new Pair (Newfirst, second)
View definition (<%)
<% relationship means that T can be implicitly converted to Comparable[t]
class pair[t <% Comparable[t]]
Context Definition
The context is defined in the form of t:m, where M is another generic class. It requires that an "implicit value" of type m[t] must exist.
Class Pair[t:ordering] (Val first:t, Val second:t) {
def smaller (implicit ord:ordering[t]) =
if (Ord.compare (fi RST, second) < 0) First else second
}
manifest Context Definition
Instantiating a generic array[t] with a base type requires a manifest[t] object.
def Makepair[t:manifest] (first:t, second:t) {
val r = new Array[t] (2)
r (0) = First
R (1) = Second
r
}
Call Makepair (4, 9), the compiler will navigate to implicit manifest[int] and actually call Makepair (4, 9) (intmanifest), which returns an array of primitive types int[2].
Multiple Definitions
can have both upper and lower bounds, but not at the same time multiple upper bound or multiple nether
T;: Lower <: Upper
//can implement multiple traits
T <: comparable[t] with Serializable W ITH cloneable
//Multiple views define
T <% comparable[t] <% String
//multiple contexts defined
t:ordering:manifest
17.3 Type Constraints
A total of three relationships: T =:= u t <:< u t <%< u
These constraints will test whether T equals U, is a subtype of u, or can be converted to u by the view (implicit). You need to add an "implicit type proof parameter" when using:
Class Pair[t] (Val first:t, Val second:t) (Implicit ev:t <:< comparable[t])
define methods in a generic class that can be used only under certain conditions
Example one:
Class Pair[t] (Val first:t, Val second:t) {
def smaller (implicit ev:t <:< ordered[t]) =
if (First < SE COND) First else second
}
//Can construct pair[file], but only if the smaller method is called
Example two: The Ornull method of the option class
Val friends = Map ("Fred", "Barney", ...)
Val friendopt = Friends.get ("Wilma")//option[string]
val friendornull = friendopt.ornull//either String or null
For value types, you can still instantiate Option[int], as long as you don't use Ornull.
Improved type inference
def Firstlast[a, C <: Iterable[a]] (IT:C) = (It.head, it.last)
//When the following code is executed: Firstlast (List (1, 2, 3)), the non-expected type is inferred [not Hing, List[int]
//Because it is a and C matching the same step, to solve this problem, you must first match C, then match A:
def Firstlast[a, C] (IT:C) (Implicit ev:c <:< Iterable[a]) = (It.head, it.last)
17.4 Type Change
co-change: C[+t]
If a is a subclass of B, then C[a] is a subclass of C[b].
Class Pair[+t] (Val first:t, Val second:t)
The + sign means that the type is covariant with T, that is, it changes in the same direction as T. Because Student is a subtype of person, Pair[student] is also a subtype of Pair[person].
inversion: c[-t]
If a is a subclass of B, then C[a] is the parent class of c[b].
Trait Friend[-t] {
def befriend (someone:t)
}
//function
def makefriendwith (S:student, f:friend[student]) { F.befriend (s)}
//Under Relationship
class Person extends Friend[person]
class Student extends person
val Susan = new S Tudent
val fred = new Person
//can use the following call, output student result, can be put into Array[person]
Makefriendwith (Susan, Fred)
17.5 covariant and contravariant points
Class Pair[+t] (Var first:t, var second:t)
Will error, said in the following setter method, the covariant type T appears in the inversion point: first_= (value:t)
The parameter position is the contravariant point, and the position of the return type is the covariant point.
In the function parameter, the type change is reversed, and the parameter is covariant. For example (Iterable[+a] of the Foldleft method):
FOLDLEFT[B] (Z:B) (OP: (b, A) = b): b
- + + - +
A is now at the covariant point.
Consider the Replacefirst method of the following immutable duality:
Class Pair[+t] (Val first:t, Val second:t) {
def replacefirst (newfirst:t) = new Pair[t] (Newfirst, second)
}
The compiler will give an error because type T appears on the contravariant point, and the workaround is to add another type parameter to the method:
Class Pair[+t] (Val first:t, Val second:t) {
def replacefirst[r;: T] (newfirst:r) = new Pair[r] (Newfirst, second)
}
17.6 object cannot be generic
You cannot add a type parameter to an object. For example:
Abstract class List[+t] {
def isempty:boolean
def head:t
def Tail:list[t]
}
class Node[t] (Val head : T, Val tail:list[t]) extends List[t] {
def isEmpty = False
}
object Empty[t] extends List[t]//Error
Workaround One:
class Empty[t] extends List[t] {
def IsEmpty = True
def head = throw new Unsupportedoperationexce Ption
def tail = throw new Unsupportedoperationexception
}
//Solution Two:
object Empty extends list[ Nothing]
//According to covariant rules, list[nothing] can be converted to List[int]
17.7 type wildcard characters
_ is a wildcard used in Scala.
Covariant
def process (people:java.util.list[_ <: person]
//Invert
import java.util.Comparator
def Min[t] ( P:pair[t]) (comp:comparator[_;: T])
17.8 Problem Solving
1. Define an immutable class Pair[t,s], with a swap method, to return the new duality of the component swap position.
2. Define a mutable class Pair[t], with a swap method, to swap the position of the components in the dual.
3. Given class Pair[t, S], write a generic method swap, accept the dual as a parameter and return the new duality of the component's swapped position.
4. In section 17.3, if we want to replace the first component of Pair[person] with student, why do we not need to set a lower bound to the Replacefirst method?
5. Why Richint achieve Comparable[int] instead of Comparable[richint]?
6. Write a generic method middle that returns the intermediate elements of any iterable[t]. For example, Middle ("world") should get ' R '.
7. View Iterable[+a] traits. Which methods used the type parameter a. Why in these methods the type parameter is in the covariant point.
8. In section 17.10, the Replacefirst method has a type definition. Why you cannot define an equivalent method on a mutable pair[t].
def Replacefirst[r;: T] (newfirst:r) {first = Newfirst}//Error
9. Restricting method parameters in an immutable class pair[+t] may seem strange. However, let's assume you can define it in pair[+t]
def replacefirst (Newfirst:t)
The problem is that the method may be rewritten (in some unreliable way). Constructs an example of this. Define a pair[double] type Nastydoublepair, override the Replacefirst method, and use the square root of the Newfirst to do the new duality. Replacefirst ("Hello") is then called for Pair[any] with the actual type Nastydoublepair.
10. Given a mutable class pair[s,t], use a type constraint to define a swap method that can be called when the type parameter is the same.
18th Chapter Advanced Type 18.1 Single Case type
Subclass Example
Class Document {
def settitle (title:string): This.type = {..., this}
def setauthor (author:string) = {..., thi s}
...
}
Class Book extends Document {
def addchapter (chapter:string) = {...
}
Notice the scarlet Letter above so that you can make the following call:
Val book = new book ()
book.settitle ("Scala for the Impatient"). Addchapter (Chapter1)
Object Example
Object Title
class Document {
private var usenextargas:any = null
def set (obj:Title.type): This.type = {Use Nextargas = obj; This}
...
}
Note that the above Scarlet letter, if used obj:title, refers to a singleton object instead of a type. 18.2 Type projection
The nested class is subordinate to its external object, as follows:
Import Scala.collection.mutable.ArrayBuffer
class Network {
class Member (Val name:string) {
val contacts = new Arraybuffer[member]
}
private val members = new Arraybuffer[member]
def join (name:string) = {
val m = new Member (name) members
+ =
m
}
}
This allows each network instance to have its own member class.
If you do not want to use "each object's own inner class" in all places, you can use the type projection network#member meaning is any network member.
Class Network {
class Member (Val name:string) {
val contacts = new Arraybuffer[network#member]
}
...
}
18.3 Path
An expression such as com.demo.chatterobj.Member is called a path. And before the final type, all components of the path must be "stable," i.e. it must be specified to a single, poor range, which must be one of the following: Package object Val This, super, Super[s], c.this, C.super, or C.super[s] 18.4 Type aliases
You can create a simple alias with the Type keyword:
Class Book {
import scala.collection.mutable._
type Index = hashmap[string, (int, int)]
...
}
You can then use Book.index instead of scala.collection.mutable.hashmap[string, (int, int)].
In addition, type aliases must be nested within a class or object, and it cannot appear at the top level of the Scala file. 18.5 Structure Type
Refers to a set of specifications for abstract methods, fields, and types. For example:
def appendlines (target: {def append (str:string): any}, lines:iterable[string]) {
18.6 Composite Types
The defined form is as follows:
T1 with T2 with T3 ...
Among them, T1, T2, T3, etc. are the types. To be an instance of this composite type, a value must meet the requirements of each type. That is, such a type is also called the intersection type.
For example:
Val image = new arraybuffer[java.awt.shape with java.io.Serializable]
//can only add those immediate shape (shape) is also an object that can be serialized.
val rect = new Rectangle (5, ten, +),
image + = rect//Ok,rectangle is Serializable's
image + = new Area (rect)/ /error, area is shape but not serializable
18.7 Middle-mounted type
A type with two type parameters, denoted by the middle syntax, and the type name is written between two type parameters, for example
String Map int
//instead of
map[string, int]
In Scala, you can define:
Type x[a, b] = (A, b)
//after which you can write
a string x int
//instead of
(string, int)
In addition, the middle-type operators have the same precedence and are left-associative. That
string x int x int
//Meaning:
((String, int), int)
18.8 Existence Type
The way to define this is to follow the forsome {...} after the type expression, with the declaration of type and Val included in the curly braces.
Array[t] Forsome {type T <: jcomponent}
Scala's type wildcard is simply the existence of a type of "syntactic sugar", such as:
Map[_, _]
//equal to
map[t, U] forsome {type T; Type U}
Only Forsome can use more complex relationships:
def process[m <: N.member forsome {val n:network}] (m1:m, m2:m) = (m1, m2)
//The method will accept members of the same network, but deny members from different networks
val chatter = new network
val myface = new network
val fred = Chatter.join ("Fred")
val wilma = Chatter.join (" Wilma ")
val Barney = Myface.join (" Barney ")
process (Fred, Wilma)//OK
process (Fred, Barney)//Error
18.9 Scala type system
type |
Grammar |
Description |
class or trait |
Class C ..., trait C ... |
5th, 10 chapters |
Meta-group |
(T1, ..., Tn) |
Section 4.7 |
function type |
(T1, ..., Tn) = = T |
|
Types with annotations |
T@a |
15th Chapter |
parameterized types |
A[t1, ..., Tn] |
17th Chapter |
Single Case Type |
Value. Type |
18th Chapter |
Type projection |
O#i |
18th Chapter |
Composite type |
T1 with T2 ... with Tn {declaration} |
18th Chapter |
Middle Type |
T1 A T2 |
18th Chapter |
Existence type |
T forsome {type and Val declaration} |
18th Chapter |
18.10 type of self
The declaration of its own type (self type) defines the trait: this: type =>, such a trait can only be mixed into subclasses of a given type.
Trait logged {
def log (msg:string)
}
trait Loggedexception extends logged {
this:exception =
def log () {log (GetMessage ())}
//Can call GetMessage because this is a exception
}
Val f = new JFrame with loggedexception
//Error: JFrame is not a subtype of Loggedexception's own type exception
When multiple types are required, a composite type can be used:
This:t with U ... =
18.11 Dependency Injection
In Scala, it is possible to achieve a simple dependency injection effect by nature and by its own type. For example log function:
Trait Logger {def log (msg:string)}
//Has two implementations of this trait, Consolelogger and FileLogger.
//user authentication trait there is a dependency on log function to record authentication failure:
trait Auth {
This:logger =
def login (id:string, password:string) : Boolean
}
//Application logic depends on these two traits:
trait app {
This:logger with Auth ...
}
Then, you can assemble our application like this:
object MyApp extends app with FileLogger ("Test.log") with Mockauth ("Users.txt")
The cake pattern gives a better design, in which a component trait is provided for each service, which includes: any dependent component that describes the attributes of the service interface in its own type an abstract Val, which will be initialized to an instance of the service can optionally contain the implementation of the Service interface
Trait loggercomponent {
trait Logger {...}
Val Logger:logger
class FileLogger (file:string) extends Logger {...}
...
}
Trait Authcomponent {
this:loggercomponent =//Allow us to access the logger
trait Auth {...}
Val Auth:auth
class Mockauth (file:string) extends Auth {...}
...
}
The component configuration
object Appcomponents extends loggercomponent with authcomponent {
val logger = new FileLogger ("Test.log ")
val auth = new Mockauth (" Users.txt ")
}
18.12 Abstract Types
A class or trait can define an abstract type that is materialized in a subclass. For example:
Trait Reader {
type Contents
def read (filename:string): Contents
}
//Here, type Contents is abstract, The specific subclass needs to specify this type:
class StringReader extends Reader {
type Contents = String
def read (filename:string) = Source . FromFile (FileName, "UTF-8"). Mkstring
}
class ImageReader extends Reader {
type Contents = bufferedimage< C10/>def Read (filename:string) = Imageio.read (new File)
}
The same effect can also be achieved by type parameters:
Trait Reader[c] {
def read (filename:string): C
}
class StringReader extends Reader[string] {
def Read (filename:string) = Source.fromfile (FileName, "UTF-8"). Mkstring
}
class ImageReader extends reader[ BufferedImage] {
def read (filename:string) = Imageio.read (new File)
}
Scala's rule of thumb is that if the type is given when the class is instantiated, the type argument is used if the type is given in the subclass, the abstract type
Abstract types are easier to use when there are multiple types of dependencies--Avoid using a long string of type parameters:
Trait Reader {
type in
type Contents
def read (in:in): Contents
}
class ImageReader extends Reader { C5/>type in = File
type Contents = BufferedImage
def read (file:in) = imageio.read (file)
}
Abstract types can have type definitions
Trait Listener {
type Event <: Java.util.EventObject
...
}
Subclasses must provide a compatible type
trait ActionListener extends Listener {
type Event = java.awt.event.ActionEvent //OK, This is a sub-type
}
18.13 Family Polymorphism
Take the example of client-side Java event handling:
generic type Example
Trait Listener[e] {def occurred (e:e): Unit}//Event source requires a collection of listeners, and a method to trigger these listeners trait source[e, L <: Listener[e]] { Private Val listeners = new Arraybuffer[l] def add (l:l) {listeners + = L} def remove (l:l) {listeners = L } def Fire (e:e) {for (L <-listeners) l.occurred (e)}}//Button trigger action event, listener type trait ActionListener exten DS Listener[actionevent]//Button class mixed with Source Trait class button extends Source[actionevent, ActionListener] {def click ()
{Fire (new ActionEvent (This, actionevent.action_performed, "click")}}} The ActionEvent class sets the event source to this, but the event source is of type object. You can use your own type to make it also type safe: trait Event[s] {var source:s = _} trait Listener[s, E <: Event[s]] {def occurred (e:e): Unit} trait Source[s, E <: Event[s], L <: Listener[s, E]] {this:s = private val listeners = new
ARRAYBUFFER[L] def Add (l:l) {listeners + = l} def remove (l:l) {listeners-L} def Fire (e:e) { E.sourCE = this//requires itself type for (l <-listeners) l.occurred (e)}}//define button