progs/lecture4.scala
changeset 222 e52cc402caee
parent 218 22705d22c105
child 223 c6453f3547ec
--- a/progs/lecture4.scala	Thu Nov 29 17:15:11 2018 +0000
+++ b/progs/lecture4.scala	Fri Nov 30 03:44:27 2018 +0000
@@ -1,3 +1,54 @@
+// Scala Lecture 4
+//=================
+
+
+// Polymorphic Types
+//===================
+
+// You do not want to write functions like contains, first, 
+// length and so on for every type of lists.
+
+
+def length_string_list(lst: List[String]): Int = lst match {
+  case Nil => 0
+  case x::xs => 1 + length_string_list(xs)
+}
+
+def length_int_list(lst: List[Int]): Int = lst match {
+  case Nil => 0
+  case x::xs => 1 + length_int_list(xs)
+}
+
+length_string_list(List("1", "2", "3", "4"))
+length_int_list(List(1, 2, 3, 4))
+
+//-----
+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))
+
+
+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 * x)
+
+
+// Remember?
+def first[A, B](xs: List[A], f: A => Option[B]) : Option[B] = ...
+
+
+// distinct / distinctBy
+
+val ls = List(1,2,3,3,2,4,3,2,1)
+ls.distinct
+
+
 def distinctBy[B, C](xs: List[B], f: B => C, acc: List[C] = Nil): List[B] = xs match {
   case Nil => Nil
   case (x::xs) => {
@@ -7,5 +58,383 @@
   }
 } 
 
+distinctBy(ls, (x: Int) => x)
+
+
+val cs = List('A', 'b', 'a', 'c', 'B', 'D', 'd')
+
+distinctBy(cs, (c:Char) => c.toUpper)
+
+
+
+// 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, but raises an exception
+// at runtime
+
+//Object[] arr = new Integer[10];
+//arr[0] = "Hello World";
+
+
+// Scala gives you a compile-time error
+
+var arr = Array[Int]()
+arr(0) = "Hello World"
+
+
+
+
+
+
+//
+// Object Oriented Programming in Scala
+//
+// =====================================
+
+abstract class Animal
+case class Bird(name: String) extends Animal
+case class Mammal(name: String) extends Animal
+case class Reptile(name: String) extends Animal
+
+println(new Bird("Sparrow"))
+println(Bird("Sparrow").toString)
+
+
+// you can override methods
+case class Bird(name: String) extends Animal {
+  override def toString = name
+}
+
+
+// There is a very convenient short-hand notation
+// for constructors
+
+class Fraction(x: Int, y: Int) {
+  def numer = x
+  def denom = y
+}
+
+
+case class Fraction(numer: Int, denom: Int)
+
+val half = Fraction(1, 2)
+
+half.denom
+
+
+// in mandelbrot.scala I used complex (imaginary) numbers and implemented
+// the usual arithmetic operations for complex numbers
+
+case class Complex(re: Double, im: Double) { 
+  // represents the complex number re + im * i
+  def +(that: Complex) = Complex(this.re + that.re, this.im + that.im)
+  def -(that: Complex) = Complex(this.re - that.re, this.im - that.im)
+  def *(that: Complex) = Complex(this.re * that.re - this.im * that.im,
+                                 this.re * that.im + that.re * this.im)
+  def *(that: Double) = Complex(this.re * that, this.im * that)
+  def abs = Math.sqrt(this.re * this.re + this.im * this.im)
+}
+
+val test = Complex(1, 2) + Complex (3, 4)
+
+// this could have equally been written as
+val test = Complex(1, 2).+(Complex (3, 4))
+
+// this applies to all methods, but requires
+import scala.language.postfixOps
+
+List(5, 2, 3, 4).sorted
+List(5, 2, 3, 4) sorted
+
+
+// to allow the notation n + m * i
+import scala.language.implicitConversions   
+object i extends Complex(0, 1)
+implicit def double2complex(re: Double) = Complex(re, 0)
+
+
+val inum1 = -2.0 + -1.5 * i
+val inum2 =  1.0 +  1.5 * i
+
+
+
+// all is public by default....so no public
+// you can have the usual restrictions about private values
+// and methods, if you are MUTABLE(!!!)
+
+case class BankAccount(init: Int) {
+
+  private var balance = init
+
+  def deposit(amount: Int): Unit = {
+    if (amount > 0) balance = balance + amount
+  }
+
+  def withdraw(amount: Int): Int =
+    if (0 < amount && amount <= balance) {
+      balance = balance - amount
+      balance
+    } else throw new Error("insufficient funds")
+}
+
+// BUT since we are IMMUTABLE, this is virtually of not 
+// concern to us.
+
+
+
+
+
+// DFAs in Scala  
+import scala.util.Try
 
 
+// A is the state type
+// C is the input (usually characters)
+
+case class DFA[A, C](start: A,               // starting state
+                     delta: (A, C) => A,     // transition function
+                     fins:  A => Boolean) {  // final states
+
+  def deltas(q: A, s: List[C]) : A = s match {
+    case Nil => q
+    case c::cs => deltas(delta(q, c), cs)
+  }
+
+  def accepts(s: List[C]) : Boolean = 
+    Try(fins(deltas(start, s))) getOrElse false
+}
+
+// the example shown in the handout 
+abstract class State
+case object Q0 extends State
+case object Q1 extends State
+case object Q2 extends State
+case object Q3 extends State
+case object Q4 extends State
+
+val delta : (State, Char) => State = 
+  { case (Q0, 'a') => Q1
+    case (Q0, 'b') => Q2
+    case (Q1, 'a') => Q4
+    case (Q1, 'b') => Q2
+    case (Q2, 'a') => Q3
+    case (Q2, 'b') => Q2
+    case (Q3, 'a') => Q4
+    case (Q3, 'b') => Q0
+    case (Q4, 'a') => Q4
+    case (Q4, 'b') => Q4 
+    case _ => throw new Exception("Undefined") }
+
+val dfa = DFA(Q0, delta, Set[State](Q4))
+
+dfa.accepts("abaaa".toList)     // true
+dfa.accepts("bbabaab".toList)   // true
+dfa.accepts("baba".toList)      // false
+dfa.accepts("abc".toList)       // false
+
+// another DFA test with a Sink state
+abstract class S
+case object S0 extends S
+case object S1 extends S
+case object S2 extends S
+case object Sink extends S
+
+// transition function with a sink state
+val sigma : (S, Char) :=> S = 
+  { case (S0, 'a') => S1
+    case (S1, 'a') => S2
+    case _ => Sink
+  }
+
+val dfa2 = DFA(S0, sigma, Set[S](S2))
+
+dfa2.accepts("aa".toList)        // true
+dfa2.accepts("".toList)          // false
+dfa2.accepts("ab".toList)        // false
+
+
+
+
+// NFAs (Nondeterministic Finite Automata)
+
+
+case class NFA[A, C](starts: Set[A],           // starting states
+                     delta: (A, C) => Set[A],  // transition function
+                     fins:  A => Boolean) {    // final states 
+
+  // given a state and a character, what is the set of 
+  // next states? if there is none => empty set
+  def next(q: A, c: C) : Set[A] = 
+    Try(delta(q, c)) getOrElse Set[A]() 
+
+  def nexts(qs: Set[A], c: C) : Set[A] =
+    qs.flatMap(next(_, c))
+
+  // depth-first version of accepts
+  def search(q: A, s: List[C]) : Boolean = s match {
+    case Nil => fins(q)
+    case c::cs => next(q, c).exists(search(_, cs))
+  }
+
+  def accepts(s: List[C]) : Boolean =
+    starts.exists(search(_, s))
+}
+
+
+
+// NFA examples
+
+val nfa_trans1 : (State, Char) => Set[State] = 
+  { case (Q0, 'a') => Set(Q0, Q1) 
+    case (Q0, 'b') => Set(Q2) 
+    case (Q1, 'a') => Set(Q1) 
+    case (Q2, 'b') => Set(Q2) }
+
+val nfa = NFA(Set[State](Q0), nfa_trans1, Set[State](Q2))
+
+nfa.accepts("aa".toList)             // false
+nfa.accepts("aaaaa".toList)          // false
+nfa.accepts("aaaaab".toList)         // true
+nfa.accepts("aaaaabbb".toList)       // true
+nfa.accepts("aaaaabbbaaa".toList)    // false
+nfa.accepts("ac".toList)             // false
+
+
+// Q: Why the kerfuffle about the polymorphic types in DFAs/NFAs
+// A: Subset construction
+
+def subset[A, C](nfa: NFA[A, C]) : DFA[Set[A], C] = {
+  DFA(nfa.starts, 
+      { case (qs, c) => nfa.nexts(qs, c) }, 
+      _.exists(nfa.fins))
+}
+
+subset(nfa1).accepts("aa".toList)             // false
+subset(nfa1).accepts("aaaaa".toList)          // false
+subset(nfa1).accepts("aaaaab".toList)         // true
+subset(nfa1).accepts("aaaaabbb".toList)       // true
+subset(nfa1).accepts("aaaaabbbaaa".toList)    // false
+subset(nfa1).accepts("ac".toList)             // false
+
+
+
+
+
+
+
+// Cool Stuff in Scala
+//=====================
+
+
+// Implicits or How to Pimp my Library
+//=====================================
+//
+// For example adding your own methods to Strings:
+// Imagine you want to increment strings, like
+//
+//     "HAL".increment
+//
+// you can avoid ugly fudges, like a MyString, by
+// using implicit conversions.
+
+
+implicit class MyString(s: String) {
+  def increment = for (c <- s) yield (c + 1).toChar 
+}
+
+"HAL".increment
+
+
+
+
+// Regular expressions - the power of DSLs in Scala
+//==================================================
+
+abstract class Rexp
+case object ZERO extends Rexp                       // nothing
+case object ONE extends Rexp                        // the empty string
+case class CHAR(c: Char) extends Rexp               // a character c
+case class ALT(r1: Rexp, r2: Rexp) extends Rexp     // alternative  r1 + r2
+case class SEQ(r1: Rexp, r2: Rexp) extends Rexp     // sequence     r1 . r2  
+case class STAR(r: Rexp) extends Rexp               // star         r*
+
+
+
+// (ab)*
+val r0 = STAR(SEQ(CHAR('a'), CHAR('b')))
+
+
+// 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
+  case c::Nil => CHAR(c)
+  case c::s => SEQ(CHAR(c), charlist2rexp(s))
+}
+implicit def string2rexp(s: String): Rexp = charlist2rexp(s.toList)
+
+
+val r1 = STAR("ab")
+val r2 = STAR(ALT("ab", "baa baa black sheep"))
+val r3 = STAR(SEQ("ab", ALT("a", "b")))
+
+implicit def RexpOps (r: Rexp) = new {
+  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
+val digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
+val sign = "+" | "-" | ""
+val number = sign ~ digit ~ digit.% 
+
+
+
+// Lazy Evaluation
+//=================
+//
+// do not evaluate arguments just yet
+
+def time_needed[T](i: Int, code: => T) = {
+  val start = System.nanoTime()
+  for (j <- 1 to i) code
+  val end = System.nanoTime()
+  (end - start)/(i * 1.0e9)
+}
+
+// same examples using the internal regexes
+val evil = "(a*)*b"
+
+("a" * 10 ++ "b").matches(evil)
+("a" * 10).matches(evil)
+("a" * 10000).matches(evil)
+("a" * 20000).matches(evil)
+
+time_needed(2, ("a" * 10000).matches(evil))