1 // Scala Lecture 4 |
1 // Scala Lecture 4 |
2 //================= |
2 //================= |
3 |
3 |
4 // pattern-matching |
4 //=================== |
|
5 // polymorphic types |
|
6 // (algebraic) datatypes and pattern-matching |
|
7 // extensions and implicits |
5 // tail-recursion |
8 // tail-recursion |
6 // polymorphic types |
9 |
|
10 |
|
11 |
|
12 // You do not want to write functions like contains, first, |
|
13 // length and so on for every type of lists. |
|
14 |
|
15 def length_int_list(lst: List[Int]): Int = lst match { |
|
16 case Nil => 0 |
|
17 case _::xs => 1 + length_int_list(xs) |
|
18 } |
|
19 |
|
20 length_int_list(List(1, 2, 3, 4)) |
|
21 |
|
22 def length_string_list(lst: List[String]): Int = lst match { |
|
23 case Nil => 0 |
|
24 case _::xs => 1 + length_string_list(xs) |
|
25 } |
|
26 |
|
27 length_string_list(List("1", "2", "3", "4")) |
|
28 |
|
29 |
|
30 // you can make the function parametric in type(s) |
|
31 |
|
32 def length[A](lst: List[A]): Int = lst match { |
|
33 case Nil => 0 |
|
34 case x::xs => 1 + length(xs) |
|
35 } |
|
36 length(List("1", "2", "3", "4")) |
|
37 length(List(1, 2, 3, 4)) |
|
38 |
|
39 |
|
40 length[String](List(1, 2, 3, 4)) |
|
41 |
|
42 |
|
43 def map[A, B](lst: List[A], f: A => B): List[B] = lst match { |
|
44 case Nil => Nil |
|
45 case x::xs => f(x)::map(xs, f) |
|
46 } |
|
47 |
|
48 map(List(1, 2, 3, 4), (x: Int) => x.toString) |
|
49 |
|
50 |
|
51 // Type inference is local in Scala |
|
52 |
|
53 def id[T](x: T) : T = x |
|
54 |
|
55 val x = id(322) // Int |
|
56 val y = id("hey") // String |
|
57 val z = id(Set(1,2,3,4)) // Set[Int] |
|
58 |
|
59 |
|
60 // The type variable concept in Scala can get |
|
61 // really complicated. |
|
62 // |
|
63 // - variance (OO) |
|
64 // - bounds (subtyping) |
|
65 // - quantification |
|
66 |
|
67 // Java has issues with this too: Java allows |
|
68 // to write the following incorrect code, and |
|
69 // only recovers by raising an exception |
|
70 // at runtime. |
|
71 |
|
72 // Object[] arr = new Integer[10]; |
|
73 // arr[0] = "Hello World"; |
|
74 |
|
75 |
|
76 // Scala gives you a compile-time error, which |
|
77 // is much better. |
|
78 |
|
79 var arr = Array[Int]() |
|
80 arr(0) = "Hello World" |
|
81 |
7 |
82 |
8 |
83 |
9 |
84 |
10 // Pattern Matching |
85 // Pattern Matching |
11 //================== |
86 //================== |
38 |
113 |
39 len(Nil) |
114 len(Nil) |
40 len(List(1,2,3,4)) |
115 len(List(1,2,3,4)) |
41 |
116 |
42 |
117 |
43 List(1,2,3,4).map(x => x * x) |
118 //================= |
44 |
119 // Trees (example of an Algebraic Datatype) |
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 |
120 |
185 abstract class Tree |
121 abstract class Tree |
186 case class Leaf(x: Int) |
122 case class Leaf(x: Int) extends Tree |
187 case class Branch(tl: Tree, tr: Tree) |
123 case class Node(s: String, left: Tree, right: Tree) extends Tree |
|
124 |
|
125 val lf = Leaf(20) |
|
126 val tr = Node("foo", Leaf(10), Leaf(23)) |
|
127 |
|
128 val lst : List[Tree] = List(lf, tr) |
|
129 |
|
130 |
188 |
131 |
189 |
132 |
190 abstract class Rexp |
133 abstract class Rexp |
191 case object ZERO extends Rexp // matches nothing |
134 case object ZERO extends Rexp // matches nothing |
192 case object ONE extends Rexp // matches the empty string |
135 case object ONE extends Rexp // matches the empty string |
355 // only optimise "self-tail calls". This excludes the cases of |
288 // only optimise "self-tail calls". This excludes the cases of |
356 // multiple functions making tail calls to each other. Well, |
289 // multiple functions making tail calls to each other. Well, |
357 // nothing is perfect. |
290 // nothing is perfect. |
358 |
291 |
359 |
292 |
360 |
293 // default arguments |
361 // Polymorphic Types |
294 |
362 //=================== |
295 def factT(n: BigInt, acc: BigInt = 1): BigInt = |
363 |
296 if (n == 0) acc else factT(n - 1, n * acc) |
364 // You do not want to write functions like contains, first, |
297 |
365 // length and so on for every type of lists. |
298 factT(1_000_000) |
366 |
299 |
367 def length_int_list(lst: List[Int]): Int = lst match { |
300 |
|
301 def length[A](xs: List[A]) : Int = xs match { |
368 case Nil => 0 |
302 case Nil => 0 |
369 case _::xs => 1 + length_int_list(xs) |
303 case _ :: tail => 1 + length(tail) |
370 } |
304 } |
371 |
305 |
372 length_int_list(List(1, 2, 3, 4)) |
306 length(List.fill(100000)(1)) |
373 |
307 |
374 def length_string_list(lst: List[String]): Int = lst match { |
308 def lengthT[A](xs: List[A], acc : Int = 0) : Int = xs match { |
375 case Nil => 0 |
309 case Nil => acc |
376 case _::xs => 1 + length_string_list(xs) |
310 case _ :: tail => lengthT(tail, 1 + acc) |
377 } |
311 } |
378 |
312 |
379 length_string_list(List("1", "2", "3", "4")) |
313 lengthT(List.fill(100000)(1)) |
380 |
314 |
381 |
315 |
382 // you can make the function parametric in type(s) |
316 |
383 |
317 |
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 |
318 |
436 |
319 |
437 |
320 |
438 |
321 |
439 // Function definitions again |
322 // Function definitions again |
728 // Imagine you want to increment strings, like |
592 // Imagine you want to increment strings, like |
729 // |
593 // |
730 // "HAL".increment |
594 // "HAL".increment |
731 // |
595 // |
732 // you can avoid ugly fudges, like a MyString, by |
596 // you can avoid ugly fudges, like a MyString, by |
733 // using implicit conversions. |
597 // using an extension. |
734 |
598 |
735 print("\n") |
599 |
736 print("""\n""") |
600 extension (s: String) { |
737 |
|
738 implicit class MyString(s: String) { |
|
739 def increment = s.map(c => (c + 1).toChar) |
601 def increment = s.map(c => (c + 1).toChar) |
740 } |
602 } |
741 |
603 |
742 "HAL".increment |
604 "HAL".increment |
743 |
605 |
744 |
606 |
745 // Abstract idea: |
|
746 // In that version implicit conversions were used to solve the |
|
747 // late extension problem; namely, given a class C and a class T, |
|
748 // how to have C extend T without touching or recompiling C. |
|
749 // Conversions add a wrapper when a member of T is requested |
|
750 // from an instance of C. |
|
751 |
607 |
752 |
608 |
753 |
609 |
754 import scala.concurrent.duration.{TimeUnit,SECONDS,MINUTES} |
610 import scala.concurrent.duration.{TimeUnit,SECONDS,MINUTES} |
755 |
611 |
756 case class Duration(time: Long, unit: TimeUnit) { |
612 case class Duration(time: Long, unit: TimeUnit) { |
757 def +(o: Duration) = |
613 def +(o: Duration) = |
758 Duration(time + unit.convert(o.time, o.unit), unit) |
614 Duration(time + unit.convert(o.time, o.unit), unit) |
759 } |
615 } |
760 |
616 |
761 implicit class Int2Duration(that: Int) { |
617 extension (that: Int) { |
762 def seconds = Duration(that, SECONDS) |
618 def seconds = Duration(that, SECONDS) |
763 def minutes = Duration(that, MINUTES) |
619 def minutes = Duration(that, MINUTES) |
764 } |
620 } |
765 |
621 |
766 5.seconds + 2.minutes //Duration(125L, SECONDS ) |
622 5.seconds + 2.minutes //Duration(125L, SECONDS ) |
787 val r0 = STAR(SEQ(CHAR('a'), CHAR('b'))) |
643 val r0 = STAR(SEQ(CHAR('a'), CHAR('b'))) |
788 |
644 |
789 |
645 |
790 // some convenience for typing in regular expressions |
646 // some convenience for typing in regular expressions |
791 import scala.language.implicitConversions |
647 import scala.language.implicitConversions |
792 import scala.language.reflectiveCalls |
|
793 |
648 |
794 def charlist2rexp(s: List[Char]): Rexp = s match { |
649 def charlist2rexp(s: List[Char]): Rexp = s match { |
795 case Nil => ONE |
650 case Nil => ONE |
796 case c::Nil => CHAR(c) |
651 case c::Nil => CHAR(c) |
797 case c::s => SEQ(CHAR(c), charlist2rexp(s)) |
652 case c::s => SEQ(CHAR(c), charlist2rexp(s)) |
798 } |
653 } |
799 |
654 |
800 implicit def string2rexp(s: String): Rexp = |
655 given Conversion[String, Rexp] = |
801 charlist2rexp(s.toList) |
656 (s => charlist2rexp(s.toList)) |
802 |
657 |
803 val r1 = STAR("ab") |
658 val r1 = STAR("ab") |
804 val r2 = STAR("hello") | STAR("world") |
659 val r2 = STAR("hello") | STAR("world") |
805 |
660 |
806 |
661 |
807 implicit def RexpOps (r: Rexp) = new { |
662 extension (r: Rexp) { |
808 def | (s: Rexp) = ALT(r, s) |
663 def | (s: Rexp) = ALT(r, s) |
809 def % = STAR(r) |
664 def % = STAR(r) |
810 def ~ (s: Rexp) = SEQ(r, s) |
665 def ~ (s: Rexp) = SEQ(r, s) |
811 } |
666 } |
812 |
667 |
813 implicit def stringOps (s: String) = new { |
|
814 def | (r: Rexp) = ALT(s, r) |
|
815 def | (r: String) = ALT(s, r) |
|
816 def % = STAR(s) |
|
817 def ~ (r: Rexp) = SEQ(s, r) |
|
818 def ~ (r: String) = SEQ(s, r) |
|
819 } |
|
820 |
|
821 //example regular expressions |
668 //example regular expressions |
|
669 |
|
670 val rex = "ab".% |
822 |
671 |
823 |
672 |
824 val digit = ("0" | "1" | "2" | "3" | "4" | |
673 val digit = ("0" | "1" | "2" | "3" | "4" | |
825 "5" | "6" | "7" | "8" | "9") |
674 "5" | "6" | "7" | "8" | "9") |
826 val sign = "+" | "-" | "" |
675 val sign = "+" | "-" | "" |