@@ -3,4 +3,279 @@ package scalatutorial.sections
33/** @param name standard_library */
44object 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}
0 commit comments