progs/lecture4.scala
changeset 494 253d1ccb65de
parent 481 e03a0100ec46
--- 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