--- 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