diff -r 244df77507c2 -r 253d1ccb65de progs/lecture4.scala --- a/progs/lecture4.scala Sun Sep 15 12:57:59 2024 +0100 +++ b/progs/lecture4.scala Mon Jul 21 16:38:07 2025 +0100 @@ -1,9 +1,84 @@ // Scala Lecture 4 //================= -// pattern-matching +//=================== +// polymorphic types +// (algebraic) datatypes and pattern-matching +// extensions and implicits // tail-recursion -// polymorphic types + + + +// You do not want to write functions like contains, first, +// length and so on for every type of lists. + +def length_int_list(lst: List[Int]): Int = lst match { + case Nil => 0 + case _::xs => 1 + length_int_list(xs) +} + +length_int_list(List(1, 2, 3, 4)) + +def length_string_list(lst: List[String]): Int = lst match { + case Nil => 0 + case _::xs => 1 + length_string_list(xs) +} + +length_string_list(List("1", "2", "3", "4")) + + +// you can make the function parametric in type(s) + +def length[A](lst: List[A]): Int = lst match { + case Nil => 0 + case x::xs => 1 + length(xs) +} +length(List("1", "2", "3", "4")) +length(List(1, 2, 3, 4)) + + +length[String](List(1, 2, 3, 4)) + + +def map[A, B](lst: List[A], f: A => B): List[B] = lst match { + case Nil => Nil + case x::xs => f(x)::map(xs, f) +} + +map(List(1, 2, 3, 4), (x: Int) => x.toString) + + +// Type inference is local in Scala + +def id[T](x: T) : T = x + +val x = id(322) // Int +val y = id("hey") // String +val z = id(Set(1,2,3,4)) // Set[Int] + + +// The type variable concept in Scala can get +// really complicated. +// +// - variance (OO) +// - bounds (subtyping) +// - quantification + +// Java has issues with this too: Java allows +// to write the following incorrect code, and +// only recovers by raising an exception +// at runtime. + +// Object[] arr = new Integer[10]; +// arr[0] = "Hello World"; + + +// Scala gives you a compile-time error, which +// is much better. + +var arr = Array[Int]() +arr(0) = "Hello World" + @@ -40,152 +115,20 @@ len(List(1,2,3,4)) -List(1,2,3,4).map(x => x * x) - -def my_map_int(lst: List[Int], f: Int => Int) : List[Int] = - lst match { - case Nil => Nil - case foo::xs => f(foo) :: my_map_int(xs, f) - } - -def my_map_option(opt: Option[Int], f: Int => Int) : Option[Int] = - opt match { - case None => None - case Some(x) => { - Some(f(x)) - } - } - -my_map_option(None, x => x * x) -my_map_option(Some(8), x => x * x) - - -// you can also have cases combined -def season(month: String) : String = month match { - case "March" | "April" | "May" => "It's spring" - case "June" | "July" | "August" => "It's summer" - case "September" | "October" | "November" => "It's autumn" - case "December" => "It's winter" - case "January" | "February" => "It's unfortunately winter" - case _ => "Wrong month" -} - -// pattern-match on integers +//================= +// Trees (example of an Algebraic Datatype) -def fib(n: Int) : Int = n match { - case 0 | 1 => 1 - case _ => fib(n - 1) + fib(n - 2) -} - -fib(10) - -// pattern-match on results - -// Silly: fizz buzz -def fizz_buzz(n: Int) : String = (n % 3, n % 5) match { - case (0, 0) => "fizz buzz" - case (0, _) => "fizz" - case (_, 0) => "buzz" - case _ => n.toString -} - -for (n <- 1 to 20) - println(fizz_buzz(n)) +abstract class Tree +case class Leaf(x: Int) extends Tree +case class Node(s: String, left: Tree, right: Tree) extends Tree -// guards in pattern-matching +val lf = Leaf(20) +val tr = Node("foo", Leaf(10), Leaf(23)) -def foo(xs: List[Int]) : String = xs match { - case Nil => s"this list is empty" - case x :: xs if x % 2 == 0 - => s"the first elemnt is even" - case x if len(x) == - => s"this list has exactly two elements" - case x :: y :: rest if x == y - => s"this has two elemnts that are the same" - case hd :: tl => s"this list is standard $hd::$tl" -} - -foo(Nil) -foo(List(1,2,3)) -foo(List(1,1)) -foo(List(1,1,2,3)) -foo(List(2,2,2,3)) - +val lst : List[Tree] = List(lf, tr) -abstract class Colour -case object Red extends Colour -case object Green extends Colour -case object Blue extends Colour -case object Yellow extends Colour - - -def fav_colour(c: Colour) : Boolean = c match { - case Green => true - case Red => true - case _ => false -} - -fav_colour(Blue) - - -// ... a tiny bit more useful: Roman Numerals - -sealed abstract class RomanDigit -case object I extends RomanDigit -case object V extends RomanDigit -case object X extends RomanDigit -case object L extends RomanDigit -case object C extends RomanDigit -case object D extends RomanDigit -case object M extends RomanDigit - -type RomanNumeral = List[RomanDigit] - -List(I, M,C,D,X,X,V,I,I, A) - -/* -I -> 1 -II -> 2 -III -> 3 -IV -> 4 -V -> 5 -VI -> 6 -VII -> 7 -VIII -> 8 -IX -> 9 -X -> 10 -*/ - -def RomanNumeral2Int(rs: RomanNumeral): Int = rs match { - case Nil => 0 - case M::r => 1000 + RomanNumeral2Int(r) - case C::M::r => 900 + RomanNumeral2Int(r) - case D::r => 500 + RomanNumeral2Int(r) - case C::D::r => 400 + RomanNumeral2Int(r) - case C::r => 100 + RomanNumeral2Int(r) - case X::C::r => 90 + RomanNumeral2Int(r) - case L::r => 50 + RomanNumeral2Int(r) - case X::L::r => 40 + RomanNumeral2Int(r) - case X::r => 10 + RomanNumeral2Int(r) - case I::X::r => 9 + RomanNumeral2Int(r) - case V::r => 5 + RomanNumeral2Int(r) - case I::V::r => 4 + RomanNumeral2Int(r) - case I::r => 1 + RomanNumeral2Int(r) -} - -RomanNumeral2Int(List(I,V)) // 4 -RomanNumeral2Int(List(I,I,I,I)) // 4 (invalid Roman number) -RomanNumeral2Int(List(V,I)) // 6 -RomanNumeral2Int(List(I,X)) // 9 -RomanNumeral2Int(List(M,C,M,L,X,X,I,X)) // 1979 -RomanNumeral2Int(List(M,M,X,V,I,I)) // 2017 - -abstract class Tree -case class Leaf(x: Int) -case class Branch(tl: Tree, tr: Tree) - abstract class Rexp case object ZERO extends Rexp // matches nothing @@ -205,18 +148,8 @@ } -// Trees (example of an Algebraic Datatype) -abstract class Tree -case class Leaf(x: Int) extends Tree -case class Node(s: String, left: Tree, right: Tree) extends Tree - -val lf = Leaf(20) -val tr = Node("foo", Leaf(10), Leaf(23)) - -val lst : List[Tree] = List(lf, tr) - // expressions (essentially trees) @@ -327,7 +260,7 @@ if (n == 0) acc else factT(n - 1, n * acc) -factT(10, 1) +factT(1000,1) println(factT(100000, 1)) @@ -357,81 +290,31 @@ // nothing is perfect. - -// Polymorphic Types -//=================== - -// You do not want to write functions like contains, first, -// length and so on for every type of lists. - -def length_int_list(lst: List[Int]): Int = lst match { - case Nil => 0 - case _::xs => 1 + length_int_list(xs) -} - -length_int_list(List(1, 2, 3, 4)) +// default arguments -def length_string_list(lst: List[String]): Int = lst match { - case Nil => 0 - case _::xs => 1 + length_string_list(xs) -} - -length_string_list(List("1", "2", "3", "4")) - - -// you can make the function parametric in type(s) +def factT(n: BigInt, acc: BigInt = 1): BigInt = + if (n == 0) acc else factT(n - 1, n * acc) -def length[A](lst: List[A]): Int = lst match { - case Nil => 0 - case x::xs => 1 + length(xs) -} -length(List("1", "2", "3", "4")) -length(List(1, 2, 3, 4)) - - -length[String](List(1, 2, 3, 4)) +factT(1_000_000) -def map[A, B](lst: List[A], f: A => B): List[B] = lst match { - case Nil => Nil - case x::xs => f(x)::map(xs, f) +def length[A](xs: List[A]) : Int = xs match { + case Nil => 0 + case _ :: tail => 1 + length(tail) } -map(List(1, 2, 3, 4), (x: Int) => x.toString) - - -// should be -def first[A, B](xs: List[A], f: A => Option[B]) : Option[B] = ??? +length(List.fill(100000)(1)) -// Type inference is local in Scala - -def id[T](x: T) : T = x +def lengthT[A](xs: List[A], acc : Int = 0) : Int = xs match { + case Nil => acc + case _ :: tail => lengthT(tail, 1 + acc) +} -val x = id(322) // Int -val y = id("hey") // String -val z = id(Set(1,2,3,4)) // Set[Int] +lengthT(List.fill(100000)(1)) -// The type variable concept in Scala can get really complicated. -// -// - variance (OO) -// - bounds (subtyping) -// - quantification - -// Java has issues with this too: Java allows -// to write the following incorrect code, and -// only recovers by raising an exception -// at runtime. - -// Object[] arr = new Integer[10]; -// arr[0] = "Hello World"; -// Scala gives you a compile-time error, which -// is much better. - -var arr = Array[Int]() -arr(0) = "Hello World" @@ -467,26 +350,6 @@ i"add ${3+2} ${3 * 3}" -// default arguments - -def length[A](xs: List[A]) : Int = xs match { - case Nil => 0 - case _ :: tail => 1 + length(tail) -} - -def lengthT[A](xs: List[A], acc : Int = 0) : Int = xs match { - case Nil => acc - case _ :: tail => lengthT(tail, 1 + acc) -} - -lengthT(List.fill(100000)(1)) - - -def fact(n: BigInt, acc: BigInt = 1): BigInt = - if (n == 0) acc else fact(n - 1, n * acc) - -fact(10) - // currying (Haskell Curry) @@ -672,7 +535,8 @@ if (isDone(game)) searchT(rest, game::sols) else { val cs = candidates(game, emptyPosition(game)) - searchT(cs.map(c => update(game, empty(game), c)) ::: rest, sols) + searchT(cs.map(c => update(game, empty(game), c)) + ::: rest, sols) } } } @@ -730,24 +594,16 @@ // "HAL".increment // // you can avoid ugly fudges, like a MyString, by -// using implicit conversions. +// using an extension. -print("\n") -print("""\n""") -implicit class MyString(s: String) { +extension (s: String) { def increment = s.map(c => (c + 1).toChar) } "HAL".increment -// Abstract idea: -// In that version implicit conversions were used to solve the -// late extension problem; namely, given a class C and a class T, -// how to have C extend T without touching or recompiling C. -// Conversions add a wrapper when a member of T is requested -// from an instance of C. @@ -758,7 +614,7 @@ Duration(time + unit.convert(o.time, o.unit), unit) } -implicit class Int2Duration(that: Int) { +extension (that: Int) { def seconds = Duration(that, SECONDS) def minutes = Duration(that, MINUTES) } @@ -789,7 +645,6 @@ // some convenience for typing in regular expressions import scala.language.implicitConversions -import scala.language.reflectiveCalls def charlist2rexp(s: List[Char]): Rexp = s match { case Nil => ONE @@ -797,28 +652,22 @@ case c::s => SEQ(CHAR(c), charlist2rexp(s)) } -implicit def string2rexp(s: String): Rexp = - charlist2rexp(s.toList) +given Conversion[String, Rexp] = + (s => charlist2rexp(s.toList)) val r1 = STAR("ab") val r2 = STAR("hello") | STAR("world") -implicit def RexpOps (r: Rexp) = new { +extension (r: Rexp) { def | (s: Rexp) = ALT(r, s) def % = STAR(r) def ~ (s: Rexp) = SEQ(r, s) } -implicit def stringOps (s: String) = new { - def | (r: Rexp) = ALT(s, r) - def | (r: String) = ALT(s, r) - def % = STAR(s) - def ~ (r: Rexp) = SEQ(s, r) - def ~ (r: String) = SEQ(s, r) -} +//example regular expressions -//example regular expressions +val rex = "ab".% val digit = ("0" | "1" | "2" | "3" | "4" | @@ -850,7 +699,7 @@ import scala.language.implicitConversions val i = Complex(0, 1) -implicit def double2complex(re: Double) = Complex(re, 0) +given Conversion[Double, Complex] = (re: Double) => Complex(re, 0) val inum1 = -2.0 + -1.5 * i val inum2 = 1.0 + 1.5 * i