Skip to content

Commit bc85d23

Browse files
committed
Next round of additions for tour
1 parent cab2792 commit bc85d23

9 files changed

+337
-6
lines changed

tour/abstract-types.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
layout: overview
3+
title: Abstract Types
4+
---
5+
6+
In Scala, classes are parameterized with values (the constructor parameters) and with types (if classes are [generic](generic-classes.html)). For reasons of regularity, it is not only possible to have values as object members; types along with values are members of objects. Furthermore, both forms of members can be concrete and abstract.
7+
Here is an example which defines both a deferred value definition and an abstract type definition as members of [class](traits.html) `Buffer`.
8+
9+
trait Buffer {
10+
type T
11+
val element: T
12+
}
13+
14+
*Abstract types* are types whose identity is not precisely known. In the example above, we only know that each object of class `Buffer` has a type member `T`, but the definition of class `Buffer` does not reveal to what concrete type the member type `T` corresponds. Like value definitions, we can override type definitions in subclasses. This allows us to reveal more information about an abstract type by tightening the type bound (which describes possible concrete instantiations of the abstract type).
15+
16+
In the following program we derive a class `SeqBuffer` which allows us to store only sequences in the buffer by stating that type `T` has to be a subtype of `Seq[U]` for a new abstract type `U`:
17+
18+
abstract class SeqBuffer extends Buffer {
19+
type U
20+
type T <: Seq[U]
21+
def length = element.length
22+
}
23+
24+
Traits or [classes](classes.html) with abstract type members are often used in combination with anonymous class instantiations. To illustrate this, we now look at a program which deals with a sequence buffer that refers to a list of integers:
25+
26+
abstract class IntSeqBuffer extends SeqBuffer {
27+
type U = Int
28+
}
29+
30+
object AbstractTypeTest1 extends Application {
31+
def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer =
32+
new IntSeqBuffer {
33+
type T = List[U]
34+
val element = List(elem1, elem2)
35+
}
36+
val buf = newIntSeqBuf(7, 8)
37+
println("length = " + buf.length)
38+
println("content = " + buf.element)
39+
}
40+
41+
The return type of method `newIntSeqBuf` refers to a specialization of trait `Buffer` in which type `U` is now equivalent to `Int`. We have a similar type alias in the anonymous class instantiation within the body of method `newIntSeqBuf`. Here we create a new instance of `IntSeqBuffer` in which type `T` refers to List[Int].
42+
43+
Please note that it is often possible to turn abstract type members into type parameters of classes and vice versa. Here is a version of the code above which only uses type parameters:
44+
45+
abstract class Buffer[+T] {
46+
val element: T
47+
}
48+
abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] {
49+
def length = element.length
50+
}
51+
object AbstractTypeTest2 extends Application {
52+
def newIntSeqBuf(e1: Int, e2: Int): SeqBuffer[Int, Seq[Int]] =
53+
new SeqBuffer[Int, List[Int]] {
54+
val element = List(e1, e2)
55+
}
56+
val buf = newIntSeqBuf(7, 8)
57+
println("length = " + buf.length)
58+
println("content = " + buf.element)
59+
}
60+
61+
Note that we have to use [variance annotations](variances.html) here; otherwise we would not be able to hide the concrete sequence implementation type of the object returned from method `newIntSeqBuf`. Furthermore, there are cases where it is not possible to replace abstract types with type parameters.
62+

tour/compound-types.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
layout: overview
3+
title: Compound Types
4+
---
5+
6+
Sometimes it is necessary to express that the type of an object is a subtype of several other types. In Scala this can be expressed with the help of *compound types*, which are intersections of object types.
7+
8+
Suppose we have two traits `Cloneable` and `Resetable`:
9+
10+
trait Cloneable extends java.lang.Cloneable {
11+
override def clone(): Cloneable = { super.clone(); this }
12+
}
13+
trait Resetable {
14+
def reset: Unit
15+
}
16+
17+
Now suppose we want to write a function `cloneAndReset` which takes an object, clones it and resets the original object:
18+
19+
def cloneAndReset(obj: ?): Cloneable = {
20+
val cloned = obj.clone()
21+
obj.reset
22+
cloned
23+
}
24+
25+
The question arises what the type of the parameter `obj` is. If it's `Cloneable` then the object can be `clone`d, but not `reset`; if it's `Resetable` we can `reset` it, but there is no `clone` operation. To avoid type casts in such a situation, we can specify the type of `obj` to be both `Cloneable` and `Resetable`. This compound type is written like this in Scala: `Cloneable with Resetable`.
26+
27+
Here's the updated function:
28+
29+
def cloneAndReset(obj: Cloneable with Resetable): Cloneable = {
30+
//...
31+
}
32+
33+
Compound types can consist of several object types and they may have a single refinement which can be used to narrow the signature of existing object members.
34+
The general form is: `A with B with C ... { refinement }`
35+
36+
An example for the use of refinements is given on the page about [abstract types](abstract-types.html).

tour/generic-classes.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
layout: overview
3+
title: Generic Classes
4+
---
5+
6+
Like in Java 5 (aka. [JDK 1.5](http://java.sun.com/j2se/1.5/)), Scala has built-in support for classes parameterized with types. Such generic classes are particularly useful for the development of collection classes.
7+
Here is an example which demonstrates this:
8+
9+
class Stack[T] {
10+
var elems: List[T] = Nil
11+
def push(x: T) { elems = x :: elems }
12+
def top: T = elems.head
13+
def pop() { elems = elems.tail }
14+
}
15+
16+
Class `Stack` models imperative (mutable) stacks of an arbitrary element type `T`. The type parameters enforces that only legal elements (that are of type `T`) are pushed onto the stack. Similarly, with type parameters we can express that method `top` will only yield elements of the given type.
17+
18+
Here are some usage examples:
19+
20+
object GenericsTest extends Application {
21+
val stack = new Stack[Int]
22+
stack.push(1)
23+
stack.push('a')
24+
println(stack.top)
25+
stack.pop()
26+
println(stack.top)
27+
}
28+
29+
The output of this program will be:
30+
31+
97
32+
1
33+
34+
_Note: subtyping of generic types is *invariant*. This means that if we have a stack of characters of type `Stack[Char]` then it cannot be used as an integer stack of type `Stack[Int]`. This would be unsound because it would enable us to enter true integers into the character stack. To conclude, `Stack[T]` is only a subtype of `Stack[S]` if and only if `S = T`. Since this can be quite restrictive, Scala offers a [type parameter annotation mechanism](variances.html) to control the subtyping behavior of generic types._

tour/index.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ Furthermore, Scala's notion of pattern matching naturally extends to the process
1515

1616
## Scala is statically typed ##
1717
Scala is equipped with an expressive type system that enforces statically that abstractions are used in a safe and coherent manner. In particular, the type system supports:
18-
* generic classes
19-
* variance annotations
20-
* upper and lower type bounds,
21-
* inner classes and abstract types as object members
22-
* compound types
18+
* [generic classes](generic-classes.html)
19+
* [variance annotations](variances.html)
20+
* [upper](upper-type-bounds.html) and [lower](lower-type-bouunds.html) type bounds,
21+
* [inner classes](inner-classes.html) and [abstract types](abstract-types.html) as object members
22+
* [compound types](compound-types.html)
2323
* explicitly typed self references
2424
* views
25-
* polymorphic methods
25+
* [polymorphic methods](polymorphic-methods.html)
2626

2727
A local type inference mechanism takes care that the user is not required to annotate the program with redundant type information. In combination, these features provide a powerful basis for the safe reuse of programming abstractions and for the type-safe extension of software.
2828

tour/inner-classes.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
---
2+
layout: overview
3+
title: Inner Classes
4+
---
5+
6+
In Scala it is possible to let classes have other classes as members. Opposed to Java-like languages where such inner classes are members of the enclosing class, in Scala such inner classes are bound to the outer object. To illustrate the difference, we quickly sketch the implementation of a graph datatype:
7+
8+
class Graph {
9+
class Node {
10+
var connectedNodes: List[Node] = Nil
11+
def connectTo(node: Node) {
12+
if (connectedNodes.find(node.equals).isEmpty) {
13+
connectedNodes = node :: connectedNodes
14+
}
15+
}
16+
}
17+
var nodes: List[Node] = Nil
18+
def newNode: Node = {
19+
val res = new Node
20+
nodes = res :: nodes
21+
res
22+
}
23+
}
24+
25+
In our program, graphs are represented by a list of nodes. Nodes are objects of inner class `Node`. Each node has a list of neighbours, which get stored in the list `connectedNodes`. Now we can set up a graph with some nodes and connect the nodes incrementally:
26+
27+
object GraphTest extends Application {
28+
val g = new Graph
29+
val n1 = g.newNode
30+
val n2 = g.newNode
31+
val n3 = g.newNode
32+
n1.connectTo(n2)
33+
n3.connectTo(n1)
34+
}
35+
36+
We now enrich the following example with types to state explicitly what the type of the various defined entities is:
37+
38+
object GraphTest extends Application {
39+
val g: Graph = new Graph
40+
val n1: g.Node = g.newNode
41+
val n2: g.Node = g.newNode
42+
val n3: g.Node = g.newNode
43+
n1.connectTo(n2)
44+
n3.connectTo(n1)
45+
}
46+
47+
This code clearly shows that a node type is prefixed with its outer instance (which is object g in our example). If we now have two graphs, the type system of Scala does not allow us to mix nodes defined within one graph with the nodes of another graph, since the nodes of the other graph have a different type.
48+
Here is an illegal program:
49+
50+
object IllegalGraphTest extends Application {
51+
val g: Graph = new Graph
52+
val n1: g.Node = g.newNode
53+
val n2: g.Node = g.newNode
54+
n1.connectTo(n2) // legal
55+
val h: Graph = new Graph
56+
val n3: h.Node = h.newNode
57+
n1.connectTo(n3) // illegal!
58+
}
59+
60+
Please note that in Java the last line in the previous example program would have been correct. For nodes of both graphs, Java would assign the same type `Graph.Node`; i.e. `Node` is prefixed with class `Graph`. In Scala such a type can be expressed as well, it is written `Graph#Node`. If we want to be able to connect nodes of different graphs, we have to change the definition of our initial graph implementation in the following way:
61+
62+
class Graph {
63+
class Node {
64+
var connectedNodes: List[Graph#Node] = Nil
65+
def connectTo(node: Graph#Node) {
66+
if (connectedNodes.find(node.equals).isEmpty) {
67+
connectedNodes = node :: connectedNodes
68+
}
69+
}
70+
}
71+
var nodes: List[Node] = Nil
72+
def newNode: Node = {
73+
val res = new Node
74+
nodes = res :: nodes
75+
res
76+
}
77+
}
78+
79+
> Please note that this program doesn't allow us to attach a node to two different graphs. If we want to remove this restriction as well, we have to change the type of variable nodes and the return type of method `newNode` to `Graph#Node`.

tour/lower-type-bounds.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
layout: overview
3+
title: Lower Type Bounds
4+
---
5+
6+
While [upper type bounds](upper-type-bounds.html) limit a type to a subtype of another type, *lower type bounds* declare a type to be a supertype of another type. The term `T >: A` expresses that the type parameter `T` or the abstract type `T` refer to a supertype of type `A`.
7+
8+
Here is an example where this is useful:
9+
10+
case class ListNode[T](h: T, t: ListNode[T]) {
11+
def head: T = h
12+
def tail: ListNode[T] = t
13+
def prepend(elem: T): ListNode[T] =
14+
ListNode(elem, this)
15+
}
16+
17+
The program above implements a linked list with a prepend operation. Unfortunately, this type is invariant in the type parameter of class `ListNode`; i.e. type `ListNode[String]` is not a subtype of type `List[Object]`. With the help of [variance annotations](variances.html) we can express such a subtype semantics:
18+
19+
case class ListNode[+T](h: T, t: ListNode[T]) { ... }
20+
21+
Unfortunately, this program does not compile, because a covariance annotation is only possible if the type variable is used only in covariant positions. Since type variable `T` appears as a parameter type of method `prepend`, this rule is broken. With the help of a *lower type bound*, though, we can implement a prepend method where `T` only appears in covariant positions.
22+
23+
Here is the corresponding code:
24+
25+
case class ListNode[+T](h: T, t: ListNode[T]) {
26+
def head: T = h
27+
def tail: ListNode[T] = t
28+
def prepend[U >: T](elem: U): ListNode[U] =
29+
ListNode(elem, this)
30+
}
31+
32+
_Note:_ the new `prepend` method has a slightly less restrictive type. It allows, for instance, to prepend an object of a supertype to an existing list. The resulting list will be a list of this supertype.
33+
34+
Here is some code which illustrates this:
35+
36+
object LowerBoundTest extends Application {
37+
val empty: ListNode[Null] = ListNode(null, null)
38+
val strList: ListNode[String] = empty.prepend("hello")
39+
.prepend("world")
40+
val anyList: ListNode[Any] = strList.prepend(12345)
41+
}
42+

tour/polymorphic-methods.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
layout: overview
3+
title: Polymorphic Methods
4+
---
5+
6+
Methods in Scala can be parameterized with both values and types. Like on the class level, value parameters are enclosed in a pair of parentheses, while type parameters are declared within a pair of brackets.
7+
8+
Here is an example:
9+
10+
object PolyTest extends Application {
11+
def dup[T](x: T, n: Int): List[T] =
12+
if (n == 0) Nil
13+
else x :: dup(x, n - 1)
14+
println(dup[Int](3, 4))
15+
println(dup("three", 3))
16+
}
17+
18+
Method `dup` in object PolyTest is parameterized with type T and with the value parameters `x: T` and `n: Int`. When method `dup` is called, the programmer provides the required parameters _(see line 5 in the program above)_, but as line 6 in the program above shows, the programmer is not required to give actual type parameters explicitly. The type system of Scala can infer such types. This is done by looking at the types of the given value parameters and at the context where the method is called.
19+
Please note that the trait `Application` is designed for writing short test programs, but should be avoided for production code (for scala versions 2.8.x and earlier) as it may affect the ability of the JVM to optimize the resulting code; please use `def main()` instead.

tour/upper-type-bounds.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
layout: overview
3+
title: Upper Type Bounds
4+
---
5+
6+
In Scala, [type parameters](generic-classes.html) and [abstract types](abstract-types.html) may be constrained by a type bound. Such type bounds limit the concrete values of the type variables and possibly reveal more information about the members of such types. An _upper type bound_ `T <: A` declares that type variable `T` refers to a subtype of type `A`.
7+
Here is an example which relies on an upper type bound for the implementation of the polymorphic method `findSimilar`:
8+
9+
trait Similar {
10+
def isSimilar(x: Any): Boolean
11+
}
12+
case class MyInt(x: Int) extends Similar {
13+
def isSimilar(m: Any): Boolean =
14+
m.isInstanceOf[MyInt] &&
15+
m.asInstanceOf[MyInt].x == x
16+
}
17+
object UpperBoundTest extends Application {
18+
def findSimilar[T <: Similar](e: T, xs: List[T]): Boolean =
19+
if (xs.isEmpty) false
20+
else if (e.isSimilar(xs.head)) true
21+
else findSimilar[T](e, xs.tail)
22+
val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3))
23+
println(findSimilar[MyInt](MyInt(4), list))
24+
println(findSimilar[MyInt](MyInt(2), list))
25+
}
26+
27+
Without the upper type bound annotation it would not be possible to call method `isSimilar` in method `findSimilar`.
28+
The usage of lower type bounds is discussed [here](lower-type-bounds.html).

tour/variances.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
layout: overview
3+
title: Variances
4+
---
5+
6+
Scala supports variance annotations of type parameters of [generic classes](generic-classes.html). In contrast to Java 5 (aka. [JDK 1.5](http://java.sun.com/j2se/1.5/)), variance annotations may be added when a class abstraction is defined, whereas in Java 5, variance annotations are given by clients when a class abstraction is used.
7+
8+
In the page about generic classes an example for a mutable stack was given. We explained that the type defined by the class `Stack[T]` is subject to invariant subtyping regarding the type parameter. This can restrict the reuse of the class abstraction. We now derive a functional (i.e. immutable) implementation for stacks which does not have this restriction. Please note that this is an advanced example which combines the use of [polymorphic methods](polymorphic-methods.html), [lower type bounds](lower-type-bounds.html), and covariant type parameter annotations in a non-trivial fashion. Furthermore we make use of [inner classes](inner-classes.html) to chain the stack elements without explicit links.
9+
10+
class Stack[+A] {
11+
def push[B >: A](elem: B): Stack[B] = new Stack[B] {
12+
override def top: B = elem
13+
override def pop: Stack[B] = Stack.this
14+
override def toString() = elem.toString() + " " +
15+
Stack.this.toString()
16+
}
17+
def top: A = error("no element on stack")
18+
def pop: Stack[A] = error("no element on stack")
19+
override def toString() = ""
20+
}
21+
22+
object VariancesTest extends Application {
23+
var s: Stack[Any] = new Stack().push("hello");
24+
s = s.push(new Object())
25+
s = s.push(7)
26+
Console.println(s)
27+
}
28+
29+
The annotation `+T` declares type `T` to be used only in covariant positions. Similarly, `-T` would declare `T` to be used only in contravariant positions. For covariant type parameters we get a covariant subtype relationship regarding this type parameter. For our example this means `Stack[T]` is a subtype of `Stack[S]` if `T` is a subtype of `S`. The opposite holds for type parameters that are tagged with a `-`.
30+
31+
For the stack example we would have to use the covariant type parameter `T` in a contravariant position for being able to define method `push`. Since we want covariant subtyping for stacks, we use a trick and abstract over the parameter type of method `push`. We get a polymorphic method in which we use the element type `T` as a lower bound of `push`'s type variable. This has the effect of bringing the variance of `T` in sync with its declaration as a covariant type parameter. Now stacks are covariant, but our solution allows that e.g. it's possible to push a string on an integer stack. The result will be a stack of type `Stack[Any]`; so only if the result is used in a context where we expect an integer stack, we actually detect the error. Otherwise we just get a stack with a more general element type.

0 commit comments

Comments
 (0)