1 // Scala Lecture 4 |
1 // Scala Lecture 4 |
2 //================= |
2 //================= |
3 |
3 |
|
4 // pattern-matching |
4 // tail-recursion |
5 // tail-recursion |
5 // polymorphic types |
6 // polymorphic types |
6 // implicits |
7 |
7 |
8 |
8 import scala.annotation.tailrec |
9 |
9 |
10 // Pattern Matching |
10 def fact(n: BigInt): BigInt = |
11 //================== |
11 if (n == 0) 1 else n * fact(n - 1) |
12 |
12 |
13 // A powerful tool which has even landed in Java during |
13 |
14 // the last few years (https://inside.java/2021/06/13/podcast-017/). |
14 def factT(n: BigInt, acc: BigInt): BigInt = |
15 // ...Scala already has it for many years and the concept is |
15 if (n == 0) acc else factT(n - 1, n * acc) |
16 // older than your friendly lecturer, that is stone old ;o) |
16 |
17 |
17 |
18 // The general schema: |
18 println(factT(1000000), 1)) |
19 // |
19 |
20 // expression match { |
20 |
21 // case pattern1 => expression1 |
21 def foo[A](args: List[A]) = ??? |
22 // case pattern2 => expression2 |
22 |
23 // ... |
23 foo(List("1","2","3","4")) |
24 // case patternN => expressionN |
24 |
25 // } |
25 |
26 |
26 // from knight1.scala |
27 |
27 def first(xs: List[Pos], f: Pos => Option[Path]) : Option[Path] = ??? |
28 // recall |
28 |
29 def len(xs: List[Int]) : Int = { |
29 // should be |
30 if (xs == Nil) 0 |
30 def first[A, B](xs: List[A], f: A => Option[B]) : Option[B] = ??? |
31 else 1 + len(xs.tail) |
|
32 } |
|
33 |
|
34 def len(xs: List[Int]) : Int = xs match { |
|
35 case Nil => 0 |
|
36 case _::xs => 1 + len(xs) |
|
37 } |
|
38 |
|
39 len(Nil) |
|
40 len(List(1,2,3,4)) |
|
41 |
|
42 |
|
43 List(1,2,3,4).map(x => x * x) |
|
44 |
|
45 def my_map_int(lst: List[Int], f: Int => Int) : List[Int] = |
|
46 lst match { |
|
47 case Nil => Nil |
|
48 case foo::xs => f(foo) :: my_map_int(xs, f) |
|
49 } |
|
50 |
|
51 def my_map_option(opt: Option[Int], f: Int => Int) : Option[Int] = |
|
52 opt match { |
|
53 case None => None |
|
54 case Some(x) => { |
|
55 Some(f(x)) |
|
56 } |
|
57 } |
|
58 |
|
59 my_map_option(None, x => x * x) |
|
60 my_map_option(Some(8), x => x * x) |
|
61 |
|
62 |
|
63 // you can also have cases combined |
|
64 def season(month: String) : String = month match { |
|
65 case "March" | "April" | "May" => "It's spring" |
|
66 case "June" | "July" | "August" => "It's summer" |
|
67 case "September" | "October" | "November" => "It's autumn" |
|
68 case "December" => "It's winter" |
|
69 case "January" | "February" => "It's unfortunately winter" |
|
70 case _ => "Wrong month" |
|
71 } |
|
72 |
|
73 // pattern-match on integers |
|
74 |
|
75 def fib(n: Int) : Int = n match { |
|
76 case 0 | 1 => 1 |
|
77 case _ => fib(n - 1) + fib(n - 2) |
|
78 } |
|
79 |
|
80 fib(10) |
|
81 |
|
82 // pattern-match on results |
|
83 |
|
84 // Silly: fizz buzz |
|
85 def fizz_buzz(n: Int) : String = (n % 3, n % 5) match { |
|
86 case (0, 0) => "fizz buzz" |
|
87 case (0, _) => "fizz" |
|
88 case (_, 0) => "buzz" |
|
89 case _ => n.toString |
|
90 } |
|
91 |
|
92 for (n <- 1 to 20) |
|
93 println(fizz_buzz(n)) |
|
94 |
|
95 // guards in pattern-matching |
|
96 |
|
97 def foo(xs: List[Int]) : String = xs match { |
|
98 case Nil => s"this list is empty" |
|
99 case x :: xs if x % 2 == 0 |
|
100 => s"the first elemnt is even" |
|
101 case x if len(x) == |
|
102 => s"this list has exactly two elements" |
|
103 case x :: y :: rest if x == y |
|
104 => s"this has two elemnts that are the same" |
|
105 case hd :: tl => s"this list is standard $hd::$tl" |
|
106 } |
|
107 |
|
108 foo(Nil) |
|
109 foo(List(1,2,3)) |
|
110 foo(List(1,1)) |
|
111 foo(List(1,1,2,3)) |
|
112 foo(List(2,2,2,3)) |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 abstract class Colour |
|
118 case object Red extends Colour |
|
119 case object Green extends Colour |
|
120 case object Blue extends Colour |
|
121 case object Yellow extends Colour |
|
122 |
|
123 |
|
124 def fav_colour(c: Colour) : Boolean = c match { |
|
125 case Green => true |
|
126 case Red => true |
|
127 case _ => false |
|
128 } |
|
129 |
|
130 fav_colour(Blue) |
|
131 |
|
132 |
|
133 // ... a tiny bit more useful: Roman Numerals |
|
134 |
|
135 sealed abstract class RomanDigit |
|
136 case object I extends RomanDigit |
|
137 case object V extends RomanDigit |
|
138 case object X extends RomanDigit |
|
139 case object L extends RomanDigit |
|
140 case object C extends RomanDigit |
|
141 case object D extends RomanDigit |
|
142 case object M extends RomanDigit |
|
143 |
|
144 type RomanNumeral = List[RomanDigit] |
|
145 |
|
146 List(I, M,C,D,X,X,V,I,I, A) |
|
147 |
|
148 /* |
|
149 I -> 1 |
|
150 II -> 2 |
|
151 III -> 3 |
|
152 IV -> 4 |
|
153 V -> 5 |
|
154 VI -> 6 |
|
155 VII -> 7 |
|
156 VIII -> 8 |
|
157 IX -> 9 |
|
158 X -> 10 |
|
159 */ |
|
160 |
|
161 def RomanNumeral2Int(rs: RomanNumeral): Int = rs match { |
|
162 case Nil => 0 |
|
163 case M::r => 1000 + RomanNumeral2Int(r) |
|
164 case C::M::r => 900 + RomanNumeral2Int(r) |
|
165 case D::r => 500 + RomanNumeral2Int(r) |
|
166 case C::D::r => 400 + RomanNumeral2Int(r) |
|
167 case C::r => 100 + RomanNumeral2Int(r) |
|
168 case X::C::r => 90 + RomanNumeral2Int(r) |
|
169 case L::r => 50 + RomanNumeral2Int(r) |
|
170 case X::L::r => 40 + RomanNumeral2Int(r) |
|
171 case X::r => 10 + RomanNumeral2Int(r) |
|
172 case I::X::r => 9 + RomanNumeral2Int(r) |
|
173 case V::r => 5 + RomanNumeral2Int(r) |
|
174 case I::V::r => 4 + RomanNumeral2Int(r) |
|
175 case I::r => 1 + RomanNumeral2Int(r) |
|
176 } |
|
177 |
|
178 RomanNumeral2Int(List(I,V)) // 4 |
|
179 RomanNumeral2Int(List(I,I,I,I)) // 4 (invalid Roman number) |
|
180 RomanNumeral2Int(List(V,I)) // 6 |
|
181 RomanNumeral2Int(List(I,X)) // 9 |
|
182 RomanNumeral2Int(List(M,C,M,L,X,X,I,X)) // 1979 |
|
183 RomanNumeral2Int(List(M,M,X,V,I,I)) // 2017 |
|
184 |
|
185 abstract class Tree |
|
186 case class Leaf(x: Int) |
|
187 case class Branch(tl: Tree, tr: Tree) |
|
188 |
|
189 |
|
190 abstract class Rexp |
|
191 case object ZERO extends Rexp // matches nothing |
|
192 case object ONE extends Rexp // matches the empty string |
|
193 case class CHAR(c: Char) extends Rexp // matches a character c |
|
194 case class ALT(r1: Rexp, r2: Rexp) extends Rexp // alternative |
|
195 case class SEQ(r1: Rexp, r2: Rexp) extends Rexp // sequence |
|
196 case class STAR(r: Rexp) extends Rexp // star |
|
197 |
|
198 def depth(r: Rexp) : Int = r match { |
|
199 case ZERO => 1 |
|
200 case ONE => 1 |
|
201 case CHAR(_) => 1 |
|
202 case ALT(r1, r2) => 1 + List(depth(r1), depth(r2)).max |
|
203 case SEQ(r1, r2) => 1 + List(depth(r1), depth(r2)).max |
|
204 case STAR(r1) => 1 + depth(r1) |
|
205 } |
|
206 |
|
207 |
|
208 // Trees (example of an Algebraic Datatype) |
|
209 |
|
210 |
|
211 abstract class Tree |
|
212 case class Leaf(x: Int) extends Tree |
|
213 case class Node(s: String, left: Tree, right: Tree) extends Tree |
|
214 |
|
215 val lf = Leaf(20) |
|
216 val tr = Node("foo", Leaf(10), Leaf(23)) |
|
217 |
|
218 val lst : List[Tree] = List(lf, tr) |
|
219 |
31 |
220 |
32 |
221 |
33 // expressions (essentially trees) |
222 // expressions (essentially trees) |
34 |
223 |
35 abstract class Exp |
224 sealed abstract class Exp |
36 case class N(n: Int) extends Exp // for numbers |
225 case class N(n: Int) extends Exp // for numbers |
37 case class Plus(e1: Exp, e2: Exp) extends Exp |
226 case class Plus(e1: Exp, e2: Exp) extends Exp |
38 case class Times(e1: Exp, e2: Exp) extends Exp |
227 case class Times(e1: Exp, e2: Exp) extends Exp |
39 |
228 |
40 def string(e: Exp) : String = e match { |
229 def string(e: Exp) : String = e match { |
114 } |
309 } |
115 |
310 |
116 comp("1 2 + 4 * 5 + 3 +".split(" ").toList.map(proc), Nil) |
311 comp("1 2 + 4 * 5 + 3 +".split(" ").toList.map(proc), Nil) |
117 |
312 |
118 |
313 |
119 // Polymorphic Types |
|
120 //=================== |
|
121 |
|
122 // You do not want to write functions like contains, first, |
|
123 // length and so on for every type of lists. |
|
124 |
|
125 def length_int_list(lst: List[Int]): Int = lst match { |
|
126 case Nil => 0 |
|
127 case _::xs => 1 + length_int_list(xs) |
|
128 } |
|
129 |
|
130 length_int_list(List(1, 2, 3, 4)) |
|
131 |
|
132 def length_string_list(lst: List[String]): Int = lst match { |
|
133 case Nil => 0 |
|
134 case _::xs => 1 + length_string_list(xs) |
|
135 } |
|
136 |
|
137 length_string_list(List("1", "2", "3", "4")) |
|
138 |
|
139 |
|
140 // you can make the function parametric in type(s) |
|
141 |
|
142 def length[A](lst: List[A]): Int = lst match { |
|
143 case Nil => 0 |
|
144 case x::xs => 1 + length(xs) |
|
145 } |
|
146 length(List("1", "2", "3", "4")) |
|
147 length(List(1, 2, 3, 4)) |
|
148 |
|
149 |
|
150 length[String](List(1, 2, 3, 4)) |
|
151 |
|
152 |
|
153 def map[A, B](lst: List[A], f: A => B): List[B] = lst match { |
|
154 case Nil => Nil |
|
155 case x::xs => f(x)::map(xs, f) |
|
156 } |
|
157 |
|
158 map(List(1, 2, 3, 4), (x: Int) => x.toString) |
|
159 |
|
160 |
|
161 // from knight1.scala |
|
162 def first(xs: List[Pos], f: Pos => Option[Path]) : Option[Path] = ??? |
|
163 |
|
164 // should be |
|
165 def first[A, B](xs: List[A], f: A => Option[B]) : Option[B] = ??? |
|
166 |
|
167 // Type inference is local in Scala |
|
168 |
|
169 def id[T](x: T) : T = x |
|
170 |
|
171 val x = id(322) // Int |
|
172 val y = id("hey") // String |
|
173 val z = id(Set(1,2,3,4)) // Set[Int] |
|
174 |
|
175 |
|
176 // The type variable concept in Scala can get really complicated. |
|
177 // |
|
178 // - variance (OO) |
|
179 // - bounds (subtyping) |
|
180 // - quantification |
|
181 |
|
182 // Java has issues with this too: Java allows |
|
183 // to write the following incorrect code, and |
|
184 // only recovers by raising an exception |
|
185 // at runtime. |
|
186 |
|
187 // Object[] arr = new Integer[10]; |
|
188 // arr[0] = "Hello World"; |
|
189 |
|
190 |
|
191 // Scala gives you a compile-time error, which |
|
192 // is much better. |
|
193 |
|
194 var arr = Array[Int]() |
|
195 arr(0) = "Hello World" |
|
196 |
|
197 |
|
198 |
|
199 |
|
200 // Function definitions again |
|
201 //============================ |
|
202 |
|
203 // variable arguments |
|
204 |
|
205 def printAll(strings: String*) = { |
|
206 strings.foreach(println) |
|
207 } |
|
208 |
|
209 printAll() |
|
210 printAll("foo") |
|
211 printAll("foo", "bar") |
|
212 printAll("foo", "bar", "baz") |
|
213 |
|
214 // pass a list to the varargs field |
|
215 val fruits = List("apple", "banana", "cherry") |
|
216 |
|
217 printAll(fruits: _*) |
|
218 |
|
219 |
|
220 // you can also implement your own string interpolations |
|
221 import scala.language.implicitConversions |
|
222 import scala.language.reflectiveCalls |
|
223 |
|
224 implicit def sring_inters(sc: StringContext) = new { |
|
225 def i(args: Any*): String = s"${sc.s(args:_*)}\n" |
|
226 } |
|
227 |
|
228 i"add ${3+2} ${3 * 3}" |
|
229 |
|
230 |
|
231 // default arguments |
|
232 |
|
233 def length[A](xs: List[A]) : Int = xs match { |
|
234 case Nil => 0 |
|
235 case _ :: tail => 1 + length(tail) |
|
236 } |
|
237 |
|
238 def lengthT[A](xs: List[A], acc : Int = 0) : Int = xs match { |
|
239 case Nil => acc |
|
240 case _ :: tail => lengthT(tail, 1 + acc) |
|
241 } |
|
242 |
|
243 lengthT(List.fill(100000)(1)) |
|
244 |
|
245 |
|
246 def fact(n: BigInt, acc: BigInt = 1): BigInt = |
|
247 if (n == 0) acc else fact(n - 1, n * acc) |
|
248 |
|
249 fact(10) |
|
250 |
|
251 |
|
252 |
|
253 // currying (Haskell Curry) |
|
254 |
|
255 def add(x: Int, y: Int) = x + y |
|
256 |
|
257 List(1,2,3,4,5).map(x => add(3, x)) |
|
258 |
|
259 def add2(x: Int)(y: Int) = x + y |
|
260 |
|
261 List(1,2,3,4,5).map(add2(3)) |
|
262 |
|
263 val a3 : Int => Int = add2(3) |
|
264 |
|
265 // currying helps sometimes with type inference |
|
266 |
|
267 def find[A](xs: List[A])(pred: A => Boolean): Option[A] = { |
|
268 xs match { |
|
269 case Nil => None |
|
270 case hd :: tl => |
|
271 if (pred(hd)) Some(hd) else find(tl)(pred) |
|
272 } |
|
273 } |
|
274 |
|
275 find(List(1, 2, 3))(x => x % 2 == 0) |
|
276 |
|
277 // Source.fromURL(url)(encoding) |
|
278 // Source.fromFile(name)(encoding) |
|
279 |
|
280 |
|
281 // Tail recursion |
314 // Tail recursion |
282 //================ |
315 //================ |
283 |
316 |
284 def fact(n: Int): Int = |
317 def fact(n: BigInt): BigInt = |
285 if (n == 0) 1 else n * fact(n - 1) |
318 if (n == 0) 1 else n * fact(n - 1) |
286 |
319 |
287 |
320 |
288 fact(10) |
321 fact(10) |
289 fact(1000) |
322 fact(1000) |
290 fact(100000) |
323 fact(100000) |
291 |
324 |
292 def factB(n: BigInt): BigInt = |
|
293 if (n == 0) 1 else n * factB(n - 1) |
|
294 |
325 |
295 def factT(n: BigInt, acc: BigInt): BigInt = |
326 def factT(n: BigInt, acc: BigInt): BigInt = |
296 if (n == 0) acc else factT(n - 1, n * acc) |
327 if (n == 0) acc else factT(n - 1, n * acc) |
297 |
328 |
298 |
329 |
299 factB(1000) |
|
300 |
|
301 |
|
302 factT(10, 1) |
330 factT(10, 1) |
303 println(factT(500000, 1)) |
331 println(factT(100000, 1)) |
304 |
332 |
305 |
333 |
306 // there is a flag for ensuring a function is tail recursive |
334 // there is a flag for ensuring a function is tail recursive |
307 import scala.annotation.tailrec |
335 import scala.annotation.tailrec |
308 |
336 |
325 // Unfortuantely, Scala because of current limitations in |
353 // Unfortuantely, Scala because of current limitations in |
326 // the JVM is not as clever as other functional languages. It can |
354 // the JVM is not as clever as other functional languages. It can |
327 // only optimise "self-tail calls". This excludes the cases of |
355 // only optimise "self-tail calls". This excludes the cases of |
328 // multiple functions making tail calls to each other. Well, |
356 // multiple functions making tail calls to each other. Well, |
329 // nothing is perfect. |
357 // nothing is perfect. |
|
358 |
|
359 |
|
360 |
|
361 // Polymorphic Types |
|
362 //=================== |
|
363 |
|
364 // You do not want to write functions like contains, first, |
|
365 // length and so on for every type of lists. |
|
366 |
|
367 def length_int_list(lst: List[Int]): Int = lst match { |
|
368 case Nil => 0 |
|
369 case _::xs => 1 + length_int_list(xs) |
|
370 } |
|
371 |
|
372 length_int_list(List(1, 2, 3, 4)) |
|
373 |
|
374 def length_string_list(lst: List[String]): Int = lst match { |
|
375 case Nil => 0 |
|
376 case _::xs => 1 + length_string_list(xs) |
|
377 } |
|
378 |
|
379 length_string_list(List("1", "2", "3", "4")) |
|
380 |
|
381 |
|
382 // you can make the function parametric in type(s) |
|
383 |
|
384 def length[A](lst: List[A]): Int = lst match { |
|
385 case Nil => 0 |
|
386 case x::xs => 1 + length(xs) |
|
387 } |
|
388 length(List("1", "2", "3", "4")) |
|
389 length(List(1, 2, 3, 4)) |
|
390 |
|
391 |
|
392 length[String](List(1, 2, 3, 4)) |
|
393 |
|
394 |
|
395 def map[A, B](lst: List[A], f: A => B): List[B] = lst match { |
|
396 case Nil => Nil |
|
397 case x::xs => f(x)::map(xs, f) |
|
398 } |
|
399 |
|
400 map(List(1, 2, 3, 4), (x: Int) => x.toString) |
|
401 |
|
402 |
|
403 // should be |
|
404 def first[A, B](xs: List[A], f: A => Option[B]) : Option[B] = ??? |
|
405 |
|
406 // Type inference is local in Scala |
|
407 |
|
408 def id[T](x: T) : T = x |
|
409 |
|
410 val x = id(322) // Int |
|
411 val y = id("hey") // String |
|
412 val z = id(Set(1,2,3,4)) // Set[Int] |
|
413 |
|
414 |
|
415 // The type variable concept in Scala can get really complicated. |
|
416 // |
|
417 // - variance (OO) |
|
418 // - bounds (subtyping) |
|
419 // - quantification |
|
420 |
|
421 // Java has issues with this too: Java allows |
|
422 // to write the following incorrect code, and |
|
423 // only recovers by raising an exception |
|
424 // at runtime. |
|
425 |
|
426 // Object[] arr = new Integer[10]; |
|
427 // arr[0] = "Hello World"; |
|
428 |
|
429 |
|
430 // Scala gives you a compile-time error, which |
|
431 // is much better. |
|
432 |
|
433 var arr = Array[Int]() |
|
434 arr(0) = "Hello World" |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 // Function definitions again |
|
440 //============================ |
|
441 |
|
442 // variable arguments |
|
443 |
|
444 def printAll(strings: String*) = { |
|
445 strings.foreach(println) |
|
446 } |
|
447 |
|
448 printAll() |
|
449 printAll("foo") |
|
450 printAll("foo", "bar") |
|
451 printAll("foo", "bar", "baz") |
|
452 |
|
453 // pass a list to the varargs field |
|
454 val fruits = List("apple", "banana", "cherry") |
|
455 |
|
456 printAll(fruits: _*) |
|
457 |
|
458 |
|
459 // you can also implement your own string interpolations |
|
460 import scala.language.implicitConversions |
|
461 import scala.language.reflectiveCalls |
|
462 |
|
463 implicit def sring_inters(sc: StringContext) = new { |
|
464 def i(args: Any*): String = s"${sc.s(args:_*)}\n" |
|
465 } |
|
466 |
|
467 i"add ${3+2} ${3 * 3}" |
|
468 |
|
469 |
|
470 // default arguments |
|
471 |
|
472 def length[A](xs: List[A]) : Int = xs match { |
|
473 case Nil => 0 |
|
474 case _ :: tail => 1 + length(tail) |
|
475 } |
|
476 |
|
477 def lengthT[A](xs: List[A], acc : Int = 0) : Int = xs match { |
|
478 case Nil => acc |
|
479 case _ :: tail => lengthT(tail, 1 + acc) |
|
480 } |
|
481 |
|
482 lengthT(List.fill(100000)(1)) |
|
483 |
|
484 |
|
485 def fact(n: BigInt, acc: BigInt = 1): BigInt = |
|
486 if (n == 0) acc else fact(n - 1, n * acc) |
|
487 |
|
488 fact(10) |
|
489 |
|
490 |
|
491 |
|
492 // currying (Haskell Curry) |
|
493 |
|
494 def add(x: Int, y: Int) = x + y |
|
495 |
|
496 List(1,2,3,4,5).map(x => add(3, x)) |
|
497 |
|
498 def add2(x: Int)(y: Int) = x + y |
|
499 |
|
500 List(1,2,3,4,5).map(add2(3)) |
|
501 |
|
502 val a3 : Int => Int = add2(3) |
|
503 |
|
504 // currying helps sometimes with type inference |
|
505 |
|
506 def find[A](xs: List[A])(pred: A => Boolean): Option[A] = { |
|
507 xs match { |
|
508 case Nil => None |
|
509 case hd :: tl => |
|
510 if (pred(hd)) Some(hd) else find(tl)(pred) |
|
511 } |
|
512 } |
|
513 |
|
514 find(List(1, 2, 3))(x => x % 2 == 0) |
|
515 |
|
516 // Source.fromURL(url)(encoding) |
|
517 // Source.fromFile(name)(encoding) |
|
518 |
|
519 |
330 |
520 |
331 |
521 |
332 |
522 |
333 |
523 |
334 |
524 |