Skip to content

Commit 1c90eb2

Browse files
committed
Add StandardLibrary section
1 parent c26221e commit 1c90eb2

File tree

2 files changed

+290
-0
lines changed

2 files changed

+290
-0
lines changed

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

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,279 @@ package scalatutorial.sections
33
/** @param name standard_library */
44
object StandardLibrary extends ScalaTutorialSection {
55

6+
/**
7+
* = List =
8+
*
9+
* The list is a fundamental data structure in functional programming.
10+
*
11+
* A list having `x1`, …, `xn` as elements is written `List(x1, …, xn)`:
12+
*
13+
* {{{
14+
* val fruit = List("apples", "oranges", "pears")
15+
* val nums = List(1, 2, 3, 4)
16+
* val diag3 = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1))
17+
* val empty = List()
18+
* }}}
19+
*
20+
* - Lists are immutable --- the elements of a list cannot be changed,
21+
* - Lists are recursive (as you will see in the next subsection),
22+
* - Lists are ''homogeneous'': the elements of a list must all have the
23+
* same type.
24+
*
25+
* The type of a list with elements of type `T` is written `List[T]`:
26+
*
27+
* {{{
28+
* val fruit: List[String] = List("apples", "oranges", "pears")
29+
* val nums : List[Int] = List(1, 2, 3, 4)
30+
* val diag3: List[List[Int]] = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1))
31+
* val empty: List[Nothing] = List()
32+
* }}}
33+
*
34+
* == Constructors of Lists ==
35+
*
36+
* Actually, all lists are constructed from:
37+
*
38+
* - the empty list `Nil`, and
39+
* - the construction operation `::` (pronounced ''cons''): `x :: xs` gives a new list
40+
* with the first element `x`, followed by the elements of `xs` (which is a list itself).
41+
*
42+
* For example:
43+
*
44+
* {{{
45+
* val fruit = "apples" :: ("oranges" :: ("pears" :: Nil))
46+
* val nums = 1 :: (2 :: (3 :: (4 :: Nil)))
47+
* val empty = Nil
48+
* }}}
49+
*
50+
* === Right Associativity ===
51+
*
52+
* Convention: Operators ending in “`:`” associate to the right.
53+
*
54+
* `A :: B :: C` is interpreted as `A :: (B :: C)`.
55+
*
56+
* We can thus omit the parentheses in the definition above.
57+
*
58+
* {{{
59+
* val nums = 1 :: 2 :: 3 :: 4 :: Nil
60+
* }}}
61+
*
62+
* Operators ending in “`:`” are also different in the they are seen as method calls of
63+
* the ''right-hand'' operand.
64+
*
65+
* So the expression above is equivalent to:
66+
*
67+
* {{{
68+
* val nums = Nil.::(4).::(3).::(2).::(1)
69+
* }}}
70+
*
71+
* == Manipulating Lists ==
72+
*
73+
* It is possible to decompose lists with pattern matching:
74+
*
75+
* - `Nil`: the `Nil` constant,
76+
* - `p :: ps`: A pattern that matches a list with a `head` matching `p` and a
77+
* `tail` matching `ps`.
78+
*
79+
* {{{
80+
* nums match {
81+
* // Lists of `Int` that starts with `1` and then `2`
82+
* case 1 :: 2 :: xs => …
83+
* // Lists of length 1
84+
* case x :: Nil => …
85+
* // Same as `x :: Nil`
86+
* case List(x) => …
87+
* // The empty list, same as `Nil`
88+
* case List() =>
89+
* // A list that contains as only element another list that starts with `2`
90+
* case List(2 :: xs) => …
91+
* }
92+
* }}}
93+
*
94+
* == Exercise: Sorting Lists ==
95+
*
96+
* Suppose we want to sort a list of numbers in ascending order:
97+
*
98+
* - One way to sort the list `List(7, 3, 9, 2)` is to sort the
99+
* tail `List(3, 9, 2)` to obtain `List(2, 3, 9)`.
100+
* - The next step is to insert the head `7` in the right place
101+
* to obtain the result `List(2, 3, 7, 9)`.
102+
*
103+
* This idea describes ''Insertion Sort'':
104+
*
105+
* {{{
106+
* def isort(xs: List[Int]): List[Int] = xs match {
107+
* case List() => List()
108+
* case y :: ys => insert(y, isort(ys))
109+
* }
110+
* }}}
111+
*
112+
* Complete the definition insertion sort by filling in the blanks in the definition below:
113+
*/
114+
def insertionSort(res0: (Int, Int) => Boolean, res1: List[Int]): Unit = {
115+
val cond: (Int, Int) => Boolean = res0
116+
def insert(x: Int, xs: List[Int]): List[Int] =
117+
xs match {
118+
case List() => res1
119+
case y :: ys =>
120+
if (cond(x, y)) x :: y :: ys
121+
else y :: insert(x, ys)
122+
}
123+
insert(2, 1 :: 3 :: Nil) shouldBe (1 :: 2 :: 3 :: Nil)
124+
insert(1, 2 :: 3 :: Nil) shouldBe (1 :: 2 :: 3 :: Nil)
125+
insert(3, 1 :: 2 :: Nil) shouldBe (1 :: 2 :: 3 :: Nil)
126+
}
127+
128+
/**
129+
* == Common Operations on Lists ==
130+
*
131+
* Transform the elements of a list using `map`:
132+
*
133+
* {{{
134+
* List(1, 2, 3).map(x => x + 1) == List(2, 3, 4)
135+
* }}}
136+
*
137+
* Filter elements using `filter`:
138+
*
139+
* {{{
140+
* List(1, 2, 3).filter(x => x % 2 == 0) == List(2)
141+
* }}}
142+
*
143+
* Transform each element of a list into a list and flatten the
144+
* results into a single list using `flatMap`:
145+
*
146+
* {{{
147+
* val xs =
148+
* List(1, 2, 3).flatMap { x =>
149+
* List(x, 2 * x, 3 * x)
150+
* }
151+
* xs == List(1, 2, 3, 2, 4, 6, 3, 6, 9)
152+
* }}}
153+
*
154+
* = Optional Values =
155+
*
156+
* We represent an optional value of type `A` with the type `Option[A]`.
157+
* This is useful to implement, for instance, partially defined
158+
* functions:
159+
*
160+
* {{{
161+
* // The `sqrt` function is not defined for negative values
162+
* def sqrt(x: Double): Option[Double] = …
163+
* }}}
164+
*
165+
* An `Option[A]` can either be `None` (if there is no value) or `Some[A]`
166+
* (if there is a value):
167+
*
168+
* {{{
169+
* def sqrt(x: Double): Option[Double] =
170+
* if (x < 0) None else Some(…)
171+
* }}}
172+
*
173+
* == Manipulating Options ==
174+
*
175+
* It is possible to decompose options with pattern matching:
176+
*
177+
* {{{
178+
* def foo(x: Double): String =
179+
* sqrt(x) match {
180+
* case None => "no result"
181+
* case Some(y) => y.toString
182+
* }
183+
* }}}
184+
*
185+
* == Common Operations on Options ==
186+
*
187+
* Transform an optional value with `map`:
188+
*/
189+
def optionMap(res0: Option[Int], res1: Option[Int]): Unit = {
190+
Some(1).map(x => x + 1) shouldBe Some(2)
191+
None.map((x: Int) => x + 1) shouldBe None
192+
res0.map(x => x + 1) shouldBe res1
193+
}
194+
195+
/**
196+
* Filter values with `filter`:
197+
*
198+
*/
199+
def optionFilter(res0: Option[Int], res1: Option[Int]): Unit = {
200+
Some(1).filter(x => x % 2 == 0) shouldBe None
201+
Some(2).filter(x => x % 2 == 0) shouldBe Some(2)
202+
res0.filter(x => x % 2 == 0) shouldBe res1
203+
}
204+
205+
/**
206+
* Use `flatMap` to transform a successful value into an optional value:
207+
*/
208+
def optionFlatMap(res0: Option[Int], res1: Option[Int]): Unit = {
209+
Some(1).flatMap(x => Some(x + 1)) shouldBe Some(2)
210+
None.flatMap((x: Int) => Some(x + 1)) shouldBe None
211+
res0.flatMap(x => Some(x + 1)) shouldBe res1
212+
}
213+
214+
/**
215+
* = Error Handling =
216+
*
217+
* This subsection introduces types that are useful to handle failures.
218+
*
219+
* == Try ==
220+
*
221+
* `Try[A]` represents a computation that attempted to return an `A`. It can
222+
* either be:
223+
* - a `Success[A]`,
224+
* - or a `Failure`.
225+
*
226+
* The key difference between `None` and `Failure`s is that the latter provide
227+
* the reason of the failure:
228+
*
229+
* {{{
230+
* def sqrt(x: Double): Try[Double] =
231+
* if (x < 0) Failure(new IllegalArgumentException("x must be positive"))
232+
* else Success(…)
233+
* }}}
234+
*
235+
* === Manipulating `Try[A]` values ===
236+
*
237+
* Like options and lists, `Try[A]` is an algebraic data type, so it can
238+
* be decomposed using pattern matching.
239+
*
240+
* `Try[A]` also have `map`, `filter` and `flatMap`. They behave the same
241+
* as with `Option[A]`, excepted that any exception that is thrown
242+
* during their execution is converted into a `Failure`.
243+
*
244+
* == Either ==
245+
*
246+
* `Either` can also be useful to handle failures. Basically, the type
247+
* `Either[A, B]` represents a value that can either be of type `A` or
248+
* of type `B`. It can be decomposed in two cases: `Left` or `Right`.
249+
*
250+
* You can use one case to represent the failure and the other to represent
251+
* the success. One difference with `Try` is that you can choose another
252+
* type than `Throwable` to represent the exception. Another difference
253+
* is that exceptions that occur when transforming `Either` values are
254+
* not converted into failures.
255+
*
256+
* {{{
257+
* def sqrt(x: Double): Either[String, Double] =
258+
* if (x < 0) Left("x must be positive")
259+
* else Right(…)
260+
* }}}
261+
*
262+
* === Manipulating `Either[A, B]` Values ===
263+
*
264+
* `Either` has `map` and `flatMap`. These methods transform the `Right`
265+
* case only. Way say that `Either` is “right biased”:
266+
*
267+
* {{{
268+
* Right(1).map((x: Int) => x + 1) shouldBe Right(2)
269+
* Left("foo").map((x: Int) => x + 1) shouldBe Left("foo")
270+
* }}}
271+
*
272+
* `Either` also has a `filterOrElse` method that turn a `Right` value
273+
* into a `Left` value if it does not satisfy a given predicate:
274+
*
275+
* {{{
276+
* Right(1).filterOrElse(x => x % 2 == 0, "Odd value") shouldBe Left("Odd value")
277+
* }}}
278+
*/
279+
def nothing(): Unit = ()
280+
6281
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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+
class StandardLibrarySpec extends Spec with Checkers {
10+
11+
def `check insertion sort`: Unit = {
12+
check(Test.testSuccess(StandardLibrary.insertionSort _, ((_: Int) < (_: Int)) :: List.empty[Int] :: HNil))
13+
}
14+
15+
}

0 commit comments

Comments
 (0)