Skip to content

Commit 5363f04

Browse files
committed
Finished type safe section of tour
1 parent bc85d23 commit 5363f04

5 files changed

+227
-3
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
layout: overview
3+
title: Explicitly Typed Self References
4+
---
5+
6+
When developing extensible software it is sometimes handy to declare the type of the value `this` explicitly. To motivate this, we will derive a small extensible representation of a graph data structure in Scala.
7+
8+
Here is a definition describing graphs:
9+
10+
abstract class Graph {
11+
type Edge
12+
type Node <: NodeIntf
13+
abstract class NodeIntf {
14+
def connectWith(node: Node): Edge
15+
}
16+
def nodes: List[Node]
17+
def edges: List[Edge]
18+
def addNode: Node
19+
}
20+
21+
Graphs consist of a list of nodes and edges where both the node and the edge type are left abstract. The use of [abstract types](abstract-types.html) allows implementations of trait Graph to provide their own concrete classes for nodes and edges. Furthermore, there is a method `addNode` for adding new nodes to a graph. Nodes are connected using `methodconnectWith`.
22+
23+
A possible implementation of class `Graph` is given in the next program:
24+
25+
abstract class DirectedGraph extends Graph {
26+
type Edge <: EdgeImpl
27+
class EdgeImpl(origin: Node, dest: Node) {
28+
def from = origin
29+
def to = dest
30+
}
31+
class NodeImpl extends NodeIntf {
32+
def connectWith(node: Node): Edge = {
33+
val edge = newEdge(this, node)
34+
edges = edge :: edges
35+
edge
36+
}
37+
}
38+
protected def newNode: Node
39+
protected def newEdge(from: Node, to: Node): Edge
40+
var nodes: List[Node] = Nil
41+
var edges: List[Edge] = Nil
42+
def addNode: Node = {
43+
val node = newNode
44+
nodes = node :: nodes
45+
node
46+
}
47+
}
48+
49+
Class `DirectedGraph` specializes the `Graph` class by providing a partial implementation. The implementation is only partial, because we would like to be able to extend `DirectedGraph` further. Therefore this class leaves all implementation details open and thus both the edge and the node type are left abstract. Nevertheless, class `DirectedGraph` reveals some additional details about the implementation of the edge type by tightening the bound to class `EdgeImpl`. Furthermore, we have some preliminary implementations of edges and nodes represented by the classes `EdgeImpl` and `NodeImpl`. Since it is necessary to create new node and edge objects within our partial graph implementation, we also have to add the factory methods `newNode` and `newEdge`. The methods `addNode` and `connectWith` are both defined in terms of these factory methods. A closer look at the implementation of method `connectWith` reveals that for creating an edge, we have to pass the self reference `this` to the factory method `newEdge`. But this is assigned the type `NodeImpl`, so it's not compatible with type `Node` which is required by the corresponding factory method. As a consequence, the program above is not well-formed and the Scala compiler will issue an error message.
50+
51+
In Scala it is possible to tie a class to another type (which will be implemented in future) by giving self reference thisthe other type explicitly. We can use this mechanism for fixing our code above. The explicit self type is specified within the body of the class `DirectedGraph`.
52+
53+
Here is the fixed program:
54+
55+
abstract class DirectedGraph extends Graph {
56+
...
57+
class NodeImpl extends NodeIntf {
58+
self: Node =>
59+
def connectWith(node: Node): Edge = {
60+
val edge = newEdge(this, node) // now legal
61+
edges = edge :: edges
62+
edge
63+
}
64+
}
65+
...
66+
}
67+
68+
In this new definition of class `NodeImpl`, `this` has type `Node`. Since type `Node` is abstract and we therefore don't know yet if `NodeImpl` is really a subtype of `Node`, the type system of Scala will not allow us to instantiate this class. But nevertheless, we state with the explicit type annotation of this that at some point, (a subclass of) `NodeImpl` has to denote a subtype of type `Node` in order to be instantiatable.
69+
70+
Here is a concrete specialization of `DirectedGraph` where all abstract class members are turned into concrete ones:
71+
72+
class ConcreteDirectedGraph extends DirectedGraph {
73+
type Edge = EdgeImpl
74+
type Node = NodeImpl
75+
protected def newNode: Node = new NodeImpl
76+
protected def newEdge(f: Node, t: Node): Edge =
77+
new EdgeImpl(f, t)
78+
}
79+
80+
Please note that in this class, we can instantiate `NodeImpl` because now we know that `NodeImpl` denotes a subtype of type `Node` (which is simply an alias for `NodeImpl`).
81+
82+
Here is a usage example of class `ConcreteDirectedGraph`:
83+
84+
object GraphTest extends Application {
85+
val g: Graph = new ConcreteDirectedGraph
86+
val n1 = g.addNode
87+
val n2 = g.addNode
88+
val n3 = g.addNode
89+
n1.connectWith(n2)
90+
n2.connectWith(n3)
91+
n1.connectWith(n3)
92+
}
93+

tour/implicit-parameters.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
layout: overview
3+
title: Implicit Parameters
4+
---
5+
6+
A method with _implicit parameters_ can be applied to arguments just like a normal method. In this case the implicit label has no effect. However, if such a method misses arguments for its implicit parameters, such arguments will be automatically provided.
7+
8+
The actual arguments that are eligible to be passed to an implicit parameter fall into two categories:
9+
* First, eligible are all identifiers x that can be accessed at the point of the method call without a prefix and that denote an implicit definition or an implicit parameter.
10+
* Second, eligible are also all members of companion modules of the implicit parameter's type that are labeled implicit.
11+
12+
In the following example we define a method `sum` which computes the sum of a list of elements using the monoid's add and unit operations. Please note that implicit values can not be top-level, they have to be members of a template.
13+
14+
abstract class SemiGroup[A] {
15+
def add(x: A, y: A): A
16+
}
17+
abstract class Monoid[A] extends SemiGroup[A] {
18+
def unit: A
19+
}
20+
object ImplicitTest extends Application {
21+
implicit object StringMonoid extends Monoid[String] {
22+
def add(x: String, y: String): String = x concat y
23+
def unit: String = ""
24+
}
25+
implicit object IntMonoid extends Monoid[Int] {
26+
def add(x: Int, y: Int): Int = x + y
27+
def unit: Int = 0
28+
}
29+
def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
30+
if (xs.isEmpty) m.unit
31+
else m.add(xs.head, sum(xs.tail))
32+
33+
println(sum(List(1, 2, 3)))
34+
println(sum(List("a", "b", "c")))
35+
}
36+
37+
38+
Here is the output of the Scala program:
39+
40+
6
41+
abc

tour/index.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ Scala is equipped with an expressive type system that enforces statically that a
2020
* [upper](upper-type-bounds.html) and [lower](lower-type-bouunds.html) type bounds,
2121
* [inner classes](inner-classes.html) and [abstract types](abstract-types.html) as object members
2222
* [compound types](compound-types.html)
23-
* explicitly typed self references
24-
* views
23+
* [explicitly typed self references](explicitly-typed-self-references.html)
24+
* [views](views.html)
2525
* [polymorphic methods](polymorphic-methods.html)
2626

27-
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.
27+
A [local type inference mechanism](local-type-inference.html) 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

2929
## Scala is extensible ##
3030

tour/local-type-inference.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
layout: overview
3+
title: Local Type Inference
4+
---
5+
Scala has a built-in type inference mechanism which allows the programmer to omit certain type annotations. It is, for instance, often not necessary in Scala to specify the type of a variable, since the compiler can deduce the type from the initialization expression of the variable. Also return types of methods can often be omitted since they corresponds to the type of the body, which gets inferred by the compiler.
6+
7+
Here is an example:
8+
9+
object InferenceTest1 extends Application {
10+
val x = 1 + 2 * 3 // the type of x is Int
11+
val y = x.toString() // the type of y is String
12+
def succ(x: Int) = x + 1 // method succ returns Int values
13+
}
14+
15+
For recursive methods, the compiler is not able to infer a result type. Here is a program which will fail the compiler for this reason:
16+
17+
object InferenceTest2 {
18+
def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1)
19+
}
20+
21+
It is also not compulsory to specify type parameters when [polymorphic methods](polymorphic-methods.html) are called or [generic classes](generic-classes.html) are instantiated. The Scala compiler will infer such missing type parameters from the context and from the types of the actual method/constructor parameters.
22+
23+
Here is an example which illustrates this:
24+
25+
case class MyPair[A, B](x: A, y: B);
26+
object InferenceTest3 extends Application {
27+
def id[T](x: T) = x
28+
val p = new MyPair(1, "scala") // type: MyPair[Int, String]
29+
val q = id(1) // type: Int
30+
}
31+
32+
The last two lines of this program are equivalent to the following code where all inferred types are made explicit:
33+
34+
val x: MyPair[Int, String] = new MyPair[Int, String](1, "scala")
35+
val y: Int = id[Int](1)
36+
37+
In some situations it can be quite dangerous to rely on Scala's type inference mechanism as the following program shows:
38+
39+
object InferenceTest4 {
40+
var obj = null
41+
obj = new Object()
42+
}
43+
44+
This program does not compile because the type inferred for variable `obj` is `Null`. Since the only value of that type is `null`, it is impossible to make this variable refer to another value.
45+

tour/views.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
layout: overview
3+
title: Views
4+
---
5+
6+
[Implicit parameters](implicit-parameters.html) and methods can also define implicit conversions called _views_. A view from type `S` to type `T` is defined by an implicit value which has function type `S => T`, or by a method convertible to a value of that type.
7+
8+
Views are applied in two situations:
9+
* If an expression `e` is of type `T`, and `T` does not conform to the expression's expected type `pt`.
10+
* In a selection `e.m` with `e` of type `T`, if the selector `m` does not denote a member of `T`.
11+
12+
In the first case, a view `v` is searched which is applicable to `e` and whose result type conforms to `pt`. In the second case, a view `v` is searched which is applicable to `e` and whose result contains a member named `m`.
13+
14+
The following operation on the two lists xs and ys of type `List[Int]` is legal:
15+
16+
xs <= ys
17+
18+
assuming the implicit methods `list2ordered` and `int2ordered` defined below are in scope:
19+
20+
implicit def list2ordered[A](x: List[A])
21+
(implicit elem2ordered: a => Ordered[A]): Ordered[List[A]] =
22+
new Ordered[List[A]] { /* .. */ }
23+
24+
implicit def int2ordered(x: Int): Ordered[Int] =
25+
new Ordered[Int] { /* .. */ }
26+
27+
The `list2ordered` function can also be expressed with the use of a _view bound_ for a type parameter:
28+
29+
implicit def list2ordered[A <% Ordered[A]](x: List[A]): Ordered[List[A]] = ...
30+
31+
The Scala compiler then generates code equivalent to the definition of `list2ordered` given above.
32+
33+
The implicitly imported object `scala.Predef` declares several predefined types (e.g. `Pair`) and methods (e.g. `error`) but also several views. The following example gives an idea of the predefined view `charWrapper`:
34+
35+
final class RichChar(c: Char) {
36+
def isDigit: Boolean = Character.isDigit(c)
37+
// isLetter, isWhitespace, etc.
38+
}
39+
object RichCharTest {
40+
implicit def charWrapper(c: char) = new RichChar(c)
41+
def main(args: Array[String]) {
42+
println('0'.isDigit)
43+
}
44+
}
45+

0 commit comments

Comments
 (0)