Skip to content

Commit b6ef557

Browse files
committed
Add TypeClass section
1 parent 14b2534 commit b6ef557

File tree

5 files changed

+245
-2
lines changed

5 files changed

+245
-2
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package scalatutorial.aux
2+
3+
class Rational(x: Int, y: Int) {
4+
5+
private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)
6+
private val g = gcd(x, y)
7+
8+
lazy val numer: Int = x / g
9+
lazy val denom: Int = y / g
10+
11+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package scalatutorial.aux
2+
3+
object Sorting {
4+
5+
def insertionSort[A](xs: List[A])(implicit ord: Ordering[A]): List[A] = {
6+
def insert(y: A, ys: List[A]): List[A] =
7+
ys match {
8+
case List() => y :: List()
9+
case z :: zs =>
10+
if (ord.lt(y, z)) y :: z :: zs
11+
else z :: insert(y, zs)
12+
}
13+
14+
xs match {
15+
case List() => List()
16+
case y :: ys => insert(y, insertionSort(ys))
17+
}
18+
}
19+
20+
}

src/main/scala/scalatutorial/sections/StandardLibrary.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,9 @@ object StandardLibrary extends ScalaTutorialSection {
103103
* This idea describes ''Insertion Sort'':
104104
*
105105
* {{{
106-
* def isort(xs: List[Int]): List[Int] = xs match {
106+
* def insertionSort(xs: List[Int]): List[Int] = xs match {
107107
* case List() => List()
108-
* case y :: ys => insert(y, isort(ys))
108+
* case y :: ys => insert(y, insertionSort(ys))
109109
* }
110110
* }}}
111111
*
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,199 @@
11
package scalatutorial.sections
22

3+
import scalatutorial.aux.Rational
4+
import scalatutorial.aux.Sorting.insertionSort
5+
36
/** @param name type_classes */
47
object TypeClasses extends ScalaTutorialSection {
58

9+
/**
10+
* Remember the sorting function:
11+
*
12+
* {{{
13+
* def insertionSort(xs: List[Int]): List[Int] = {
14+
* def insert(y: Int, ys: List[Int]): List[Int] =
15+
* ys match {
16+
* case List() => y :: List()
17+
* case z :: zs =>
18+
* if (y < z) y :: z :: zs
19+
* else z :: insert(y, zs)
20+
* }
21+
*
22+
* xs match {
23+
* case List() => List()
24+
* case y :: ys => insert(y, insertionSort(ys))
25+
* }
26+
* }
27+
*
28+
* }}}
29+
*
30+
* How to parameterize `insertionSort` so that it can also be used for
31+
* lists with elements other than `Int` (like, for instance, `Rational`)?
32+
*
33+
* {{{
34+
* def insertionSort[T](xs: List[T]): List[T] = ...
35+
* }}}
36+
*
37+
* The above attempt does not work, because the comparison `<` in `insert`
38+
* is not defined for arbitrary types `T`.
39+
*
40+
* Idea: parameterize `insert` with the necessary comparison function.
41+
*
42+
* = Parameterization of Sort =
43+
*
44+
* The most flexible design is to make the function `insertionSort`
45+
* polymorphic and to pass the comparison operation as an additional
46+
* parameter:
47+
*
48+
* {{{
49+
* def insertionSort[T](xs: List[T])(lessThan: (T, T) => Boolean) = {
50+
* def insert(y: Int, ys: List[Int]): List[Int] =
51+
* ys match {
52+
* …
53+
* case z :: zs =>
54+
* if (lessThan(y, z)) y :: z :: zs
55+
* else …
56+
* }
57+
*
58+
* xs match {
59+
* …
60+
* case y :: ys => insert(y, insertionSort(ys)(lessThan))
61+
* }
62+
* }
63+
*
64+
* = Calling Parameterized Sort =
65+
*
66+
* We can now call `insertionSort` as follows:
67+
*
68+
* {{{
69+
* val nums = List(-5, 6, 3, 2, 7)
70+
* val fruit = List("apple", "pear", "orange", "pineapple")
71+
*
72+
* insertionSort(nums)((x: Int, y: Int) => x < y)
73+
* insertionSort(fruit)((x: String, y: String) => x.compareTo(y) < 0)
74+
* }}}
75+
*
76+
* Or, since parameter types can be inferred from the call `insertionSort(xs)`:
77+
*
78+
* {{{
79+
* insertionSort(nums)((x, y) => x < y)
80+
* }}}
81+
*
82+
* = Parametrization with Ordered =
83+
*
84+
* There is already a class in the standard library that represents orderings.
85+
*
86+
* {{{
87+
* scala.math.Ordering[T]
88+
* }}}
89+
*
90+
* provides ways to compare elements of type `T`. So instead of
91+
* parameterizing with the `lessThan` operation directly, we could parameterize
92+
* with `Ordering` instead:
93+
*
94+
* {{{
95+
* def insertionSort[T](xs: List[T])(ord: Ordering[T]): List[T] = {
96+
* def insert(y: Int, ys: List[Int]): List[Int] =
97+
* … if (ord.lt(y, z)) …
98+
*
99+
* … insert(y, insertionSort(ys)(ord)) …
100+
* }
101+
* }}}
102+
*
103+
* = Ordered Instances: =
104+
*
105+
* Calling the new `insertionSort` can be done like this:
106+
*
107+
* {{{
108+
* insertionSort(nums)(Ordering.Int)
109+
* insertionSort(fruits)(Ordering.String)
110+
* }}}
111+
*
112+
* This makes use of the values `Int` and `String` defined in the
113+
* `scala.math.Ordering` object, which produce the right orderings on
114+
* integers and strings.
115+
*
116+
* = Implicit Parameters =
117+
*
118+
* Problem: Passing around `lessThan` or `ord` values is cumbersome.
119+
*
120+
* We can avoid this by making `ord` an implicit parameter:
121+
*
122+
* {{{
123+
* def insertionSort[T](xs: List[T])(implicit ord: Ordering[T]): List[T] = {
124+
* def insert(y: Int, ys: List[Int]): List[Int] =
125+
* … if (ord.lt(y, z)) …
126+
*
127+
* … insert(y, insertionSort(ys)) …
128+
* }
129+
* }}}
130+
*
131+
* Then calls to `insertionSort` can avoid the ordering parameters:
132+
*
133+
* {{{
134+
* insertionSort(nums)
135+
* insertionSort(fruits)
136+
* }}}
137+
*
138+
* The compiler will figure out the right implicit to pass based on the
139+
* demanded type.
140+
*
141+
* = Rules for Implicit Parameters =
142+
*
143+
* Say, a function takes an implicit parameter of type `T`.
144+
*
145+
* The compiler will search an implicit definition that
146+
*
147+
* - is marked `implicit`
148+
* - has a type compatible with `T`
149+
* - is visible at the point of the function call, or is defined
150+
* in a companion object associated with `T`.
151+
*
152+
* If there is a single (most specific) definition, it will be taken as
153+
* actual argument for the implicit parameter. Otherwise it's an error.
154+
*
155+
* = Type Classes =
156+
*
157+
* The combination of types parameterized and implicit parameters is also
158+
* called ''type classes''.
159+
*
160+
* = Exercises =
161+
*
162+
* Define an ordering for the `Rational` type:
163+
*
164+
* {{{
165+
* class Rational(x: Int, y: Int) {
166+
*
167+
* private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)
168+
* private val g = gcd(x, y)
169+
*
170+
* lazy val numer: Int = x / g
171+
* lazy val denom: Int = y / g
172+
* }
173+
* }}}
174+
*/
175+
def rationalOrdering(res0: (Rational, Rational) => Int): Unit = {
176+
/**
177+
* Returns an integer whose sign communicates how the first parameter
178+
* compares to the second parameter.
179+
*
180+
* The result sign has the following meaning:
181+
* - Negative if the first parameter is less than the second parameter
182+
* - Positive if the first parameter is greater than the second parameter
183+
* - Zero otherwise
184+
*/
185+
val compareRationals: (Rational, Rational) => Int = res0
186+
187+
implicit val rationalOrder: Ordering[Rational] =
188+
new Ordering[Rational] {
189+
def compare(x: Rational, y: Rational): Int = compareRationals(x, y)
190+
}
191+
192+
val half = new Rational(1, 2)
193+
val third = new Rational(1, 3)
194+
val fourth = new Rational(1, 4)
195+
val rationals = List(third, half, fourth)
196+
insertionSort(rationals) shouldBe List(fourth, third, half)
197+
}
198+
6199
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package scalatutorial.sections
2+
3+
import org.scalacheck.Shapeless._
4+
import org.scalaexercises.Test
5+
import org.scalatest.Spec
6+
import org.scalatest.prop.Checkers
7+
import shapeless.HNil
8+
9+
import scalatutorial.aux.Rational
10+
11+
class TypeClassesSpec extends Spec with Checkers {
12+
13+
def `check rational ordering`: Unit = {
14+
val ordering =
15+
(x: Rational, y: Rational) => x.numer * y.denom - y.numer * x.denom
16+
check(Test.testSuccess(TypeClasses.rationalOrdering _, ordering :: HNil))
17+
}
18+
19+
}

0 commit comments

Comments
 (0)