progs/lecture5.scala
changeset 326 e5453add7df6
parent 247 50a3b874008a
child 328 0e591f806290
--- a/progs/lecture5.scala	Tue Nov 26 01:22:36 2019 +0000
+++ b/progs/lecture5.scala	Tue Dec 03 01:22:16 2019 +0000
@@ -7,29 +7,30 @@
 //=====================
 
 // The concept of lazy evaluation doesn’t really 
-// exist in non-functional languages, but it is 
-// pretty easy to grasp. Consider first 
+// exist in non-functional languages. C-like languages
+// are strict. To see the difference, consider
 
 def square(x: Int) = x * x
 
 square(42 + 8)
 
-// this is called strict evaluation
+// This is called "strict evaluation".
 
-// say we have a pretty expensive operation
+// In contrast, say we have a pretty expensive operation:
+
 def peop(n: BigInt): Boolean = peop(n + 1) 
 
 val a = "foo"
-val b = "bar"
+val b = "foo"
 
 if (a == b || peop(0)) println("true") else println("false")
 
-// this is called lazy evaluation
+// This is called "lazy evaluation":
 // you delay compuation until it is really 
-// needed; once calculated though, does not 
-// need to be re-calculated
+// needed. Once calculated though, the result
+// does not need to be re-calculated.
 
-// a useful example is
+// A useful example is
 def time_needed[T](i: Int, code: => T) = {
   val start = System.nanoTime()
   for (j <- 1 to i) code
@@ -37,42 +38,38 @@
   f"${(end - start) / (i * 1.0e9)}%.6f secs"
 }
 
+// A slightly less obvious example: Prime Numbers.
+// (I do not care how many) primes: 2, 3, 5, 7, 9, 11, 13 ....
 
-// streams (I do not care how many)
-// primes: 2, 3, 5, 7, 9, 11, 13 ....
-
-def generatePrimes (s: Stream[Int]): Stream[Int] =
+def generatePrimes (s: LazyList[Int]): LazyList[Int] =
   s.head #:: generatePrimes(s.tail.filter(_ % s.head != 0))
 
-val primes = generatePrimes(Stream.from(2))
+val primes = generatePrimes(LazyList.from(2))
 
 // the first 10 primes
-primes.take(10).par.toList
+primes.take(10).toList
 
 time_needed(1, primes.filter(_ > 100).take(3000).toList)
-time_needed(1, primes.filter(_ > 100).take(1000).toList)
+time_needed(1, primes.filter(_ > 100).take(3000).toList)
 
-// a stream of successive numbers
+// A Stream (LazyList) of successive numbers:
 
-Stream.from(2).print
-Stream.from(2).take(10).force
-Stream.from(2).take(10).print
-Stream.from(10).take(10).print
+LazyList.from(2).take(10)
+LazyList.from(2).take(10).force
 
-Stream.from(2).take(10).force
-
-// iterative version of the Fibonacci numbers
-def fibIter(a: BigInt, b: BigInt): Stream[BigInt] =
+// An Iterative version of the Fibonacci numbers
+def fibIter(a: BigInt, b: BigInt): LazyList[BigInt] =
   a #:: fibIter(b, a + b)
 
 
 fibIter(1, 1).take(10).force
 fibIter(8, 13).take(10).force
 
-fibIter(1, 1).drop(10000).take(1).print
+fibIter(1, 1).drop(10000).take(1)
+fibIter(1, 1).drop(10000).take(1).force
 
 
-// good for testing
+// LazyLists are good for testing
 
 
 // Regular expressions - the power of DSLs in Scala
@@ -100,7 +97,6 @@
 implicit def string2rexp(s: String): Rexp = 
   charlist2rexp(s.toList)
 
-
 implicit def RexpOps (r: Rexp) = new {
   def | (s: Rexp) = ALT(r, s)
   def % = STAR(r)
@@ -130,7 +126,7 @@
 val sign = "+" | "-" | ""
 val number = sign ~ digit ~ digit.% 
 
-// task: enumerate exhaustively regular expression
+// Task: enumerate exhaustively regular expressions
 // starting from small ones towards bigger ones.
 
 // 1st idea: enumerate them all in a Set
@@ -149,154 +145,365 @@
 enuml(1, "a")
 enuml(1, "a").size
 enuml(2, "a").size
-enuml(3, "a").size 
-enuml(4, "a").size // out of heap space
+enuml(3, "a").size // out of heap space
 
 
-def enum(rs: Stream[Rexp]) : Stream[Rexp] = 
+
+def enum(rs: LazyList[Rexp]) : LazyList[Rexp] = 
   rs #::: enum( (for (r1 <- rs; r2 <- rs) yield ALT(r1, r2)) #:::
                 (for (r1 <- rs; r2 <- rs) yield SEQ(r1, r2)) #:::
                 (for (r1 <- rs) yield STAR(r1)) )
 
 
-enum(ZERO #:: ONE #:: "ab".toStream.map(CHAR)).take(200).force
-enum(ZERO #:: ONE #:: "ab".toStream.map(CHAR)).take(5000000)
+enum(LazyList(ZERO, ONE, CHAR('a'), CHAR('b'))).take(200).force
+enum(LazyList(ZERO, ONE, CHAR('a'), CHAR('b'))).take(5000000)
 
 
 val is = 
-  (enum(ZERO #:: ONE #:: "ab".toStream.map(CHAR))
+  (enum(LazyList(ZERO, ONE, CHAR('a'), CHAR('b')))
     .dropWhile(depth(_) < 3)
     .take(10).foreach(println))
 
 
+// Polymorphic Types
+//===================
 
-// Parsing - The Solved Problem That Isn't
-//=========================================
-//
-// https://tratt.net/laurie/blog/entries/parsing_the_solved_problem_that_isnt.html
-//
-// Or, A topic of endless "fun"(?)
+// 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))
+
+// 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))
+
+
+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)
+
 
 
-// input type: String
-// output type: Int
-Integer.parseInt("123u456")
+// distinct / distinctBy
+
+val ls = List(1,2,3,3,2,4,3,2,1)
+ls.distinct
+
+// .minBy(_._2)
+// .sortBy(_._1)
+
+def distinctBy[B, C](xs: List[B], 
+                     f: B => C, 
+                     acc: List[C] = Nil): List[B] = xs match {
+  case Nil => Nil
+  case x::xs => {
+    val res = f(x)
+    if (acc.contains(res)) distinctBy(xs, f, acc)  
+    else x::distinctBy(xs, f, res::acc)
+  }
+} 
+
+val cs = List('A', 'b', 'a', 'c', 'B', 'D', 'd')
+
+distinctBy(cs, (c:Char) => c.toUpper)
+
+// since 2.13
+
+cs.distinctBy((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
 
-/* Note, in the previous lectures I did not show the type consraint
- * I <% Seq[_] , which means that the input type I can be
- * treated, or seen, as a sequence. */
+// 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"
+
+
+
+// (Immutable)
+// Object Oriented Programming in Scala
+//
+// =====================================
 
-abstract class Parser[I <% Seq[_], T] {
-  def parse(ts: I): Set[(T, I)]
+abstract class Animal
+case class Bird(name: String) extends Animal {
+   override def toString = name
+}
+case class Mammal(name: String) extends Animal
+case class Reptile(name: String) extends Animal
+
+Mammal("Zebra")
+println(Mammal("Zebra"))
+println(Mammal("Zebra").toString)
+
 
-  def parse_all(ts: I) : Set[T] =
-    for ((head, tail) <- parse(ts); 
-        if (tail.isEmpty)) yield head
+Bird("Sparrow")
+println(Bird("Sparrow"))
+println(Bird("Sparrow").toString)
+
+
+// There is a very convenient short-hand notation
+// for constructors:
+
+class Fraction(x: Int, y: Int) {
+  def numer = x
+  def denom = y
 }
 
-// the idea is that a parser can parse something
-// from the input and leaves something unparsed => pairs
+val half = new Fraction(1, 2)
+
+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   
+
+val i = Complex(0, 1)
+implicit def double2complex(re: Double) = Complex(re, 0)
+
 
-class AltParser[I <% Seq[_], T](
-  p: => Parser[I, T], 
-  q: => Parser[I, T]) extends Parser[I, T] {
+val inum1 = -2.0 + -1.5 * i
+val inum2 =  1.0 +  1.5 * i
+
+
+
+// All is public by default....so no public is needed.
+// 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 parse(sb: I) = p.parse(sb) ++ q.parse(sb)   
+  def withdraw(amount: Int): Int =
+    if (0 < amount && amount <= balance) {
+      balance = balance - amount
+      balance
+    } else throw new Error("insufficient funds")
 }
 
+// BUT since we are completely IMMUTABLE, this is 
+// virtually of not concern to us.
+
+
+
+// another example about Fractions
+import scala.language.implicitConversions
+import scala.language.reflectiveCalls
+
+
+case class Fraction(numer: Int, denom: Int) {
+  override def toString = numer.toString + "/" + denom.toString
+
+  def +(other: Fraction) = Fraction(numer + other.numer, denom + other.denom)
+  def /(other: Fraction) = Fraction(numer * other.denom, denom * other.numer)
+ }
+
+implicit def Int2Fraction(x: Int) = Fraction(x, 1)
+
 
-class SeqParser[I <% Seq[_], T, S](
-  p: => Parser[I, T], 
-  q: => Parser[I, S]) extends Parser[I, (T, S)] {
+val half = Fraction(1, 2)
+val third = Fraction (1, 3)
+
+half + third
+half / third
+
+(1 / 3) + half
+(1 / 2) + third
+
+
+
+
+// DFAs in Scala  
+//===============
+import scala.util.Try
 
-  def parse(sb: I) = 
-    for ((head1, tail1) <- p.parse(sb); 
-         (head2, tail2) <- q.parse(tail1)) yield ((head1, head2), tail2)
+
+// 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 (Set)
+
+  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
 
-class FunParser[I <% Seq[_], T, S](
-  p: => Parser[I, T], 
-  f: T => S) extends Parser[I, S] {
+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
+
 
-  def parse(sb: I) = 
-    for ((head, tail) <- p.parse(sb)) yield (f(head), tail)
+// 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))
 }
 
 
-// atomic parsers  
-case class CharParser(c: Char) extends Parser[String, Char] {
-  def parse(sb: String) = 
-    if (sb != "" && sb.head == c) Set((c, sb.tail)) else Set()
-}
+
+// 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) }
 
-import scala.util.matching.Regex
-case class RegexParser(reg: Regex) extends Parser[String, String] {
-  def parse(sb: String) = reg.findPrefixMatchOf(sb) match {
-    case None => Set()
-    case Some(m) => Set((m.matched, m.after.toString))  
-  }
-}
+val nfa = NFA(Set[State](Q0), nfa_trans1, Set[State](Q2))
 
-val NumParser = RegexParser("[0-9]+".r)
-def StringParser(s: String) = RegexParser(Regex.quote(s).r)
-
-NumParser.parse_all("12u345")
-println(NumParser.parse_all("12u45"))
+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
 
 
-// convenience
-implicit def string2parser(s: String) = StringParser(s)
-implicit def char2parser(c: Char) = CharParser(c)
+// Q: Why the kerfuffle about the polymorphic types in DFAs/NFAs?
+// A: Subset construction. Here the state type for the DFA is
+//    sets of states.
 
-implicit def ParserOps[I<% Seq[_], T](p: Parser[I, T]) = new {
-  def | (q : => Parser[I, T]) = new AltParser[I, T](p, q)
-  def ==>[S] (f: => T => S) = new FunParser[I, T, S](p, f)
-  def ~[S] (q : => Parser[I, S]) = new SeqParser[I, T, S](p, q)
+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))
 }
 
-implicit def StringOps(s: String) = new {
-  def | (q : => Parser[String, String]) = new AltParser[String, String](s, q)
-  def | (r: String) = new AltParser[String, String](s, r)
-  def ==>[S] (f: => String => S) = new FunParser[String, String, S](s, f)
-  def ~[S] (q : => Parser[String, S]) = 
-    new SeqParser[String, String, S](s, q)
-  def ~ (r: String) = 
-    new SeqParser[String, String, String](s, r)
-}
-
-
-val NumParserInt = NumParser ==> (s => 2 * s.toInt)
-
-NumParser.parse_all("12345")
-NumParserInt.parse_all("12345")
-NumParserInt.parse_all("12u45")
-
-
-// grammar for arithmetic expressions
-//
-//  E ::= T + E | T - E | T
-//  T ::= F * T | F
-//  F ::= ( E ) | Number
-
-
-lazy val E: Parser[String, Int] = 
-  (T ~ "+" ~ E) ==> { case ((x, y), z) => x + z } |
-  (T ~ "-" ~ E) ==> { case ((x, y), z) => x - z } | T 
-lazy val T: Parser[String, Int] = 
-  (F ~ "*" ~ T) ==> { case ((x, y), z) => x * z } | F
-lazy val F: Parser[String, Int] = 
-  ("(" ~ E ~ ")") ==> { case ((x, y), z) => y } | NumParserInt
-
-
-println(E.parse_all("4*2+3"))
-println(E.parse_all("4*(2+3)"))
-println(E.parse_all("(4)*((2+3))"))
-println(E.parse_all("4/2+3"))
-println(E.parse_all("(1+2)+3"))
-println(E.parse_all("1+2+3")) 
-
-
+subset(nfa).accepts("aa".toList)             // false
+subset(nfa).accepts("aaaaa".toList)          // false
+subset(nfa).accepts("aaaaab".toList)         // true
+subset(nfa).accepts("aaaaabbb".toList)       // true
+subset(nfa).accepts("aaaaabbbaaa".toList)    // false
+subset(nfa).accepts("ac".toList)             // false
 
 
 
@@ -308,9 +515,9 @@
 // A function should do one thing, and only one thing.
 
 // Make your variables immutable, unless there's a good 
-// reason not to.
+// reason not to. Usually there is not.
 
-// I did it, but this is actually not a good reason:
+// I did it once, but this is actually not a good reason:
 // generating new labels:
 
 var counter = -1
@@ -325,15 +532,69 @@
 
 
 
-// You can be productive on Day 1, but the language is deep.
+// I think you can be productive on Day 1, but the 
+// language is deep.
 //
 // http://scalapuzzlers.com
 //
 // http://www.latkin.org/blog/2017/05/02/when-the-scala-compiler-doesnt-help/
 
-List(1, 2, 3).contains("your mom")
+List(1, 2, 3).contains("your cup")
+
 
 // I like best about Scala that it lets me often write
 // concise, readable code. And it hooks up with the 
-// Isabelle theorem prover.
+// Isabelle theorem prover. 
+
+
+// Puzzlers
+
+val MONTH = 12
+val DAY = 24
+val (HOUR, MINUTE, SECOND) = (12, 0, 0)
+
+// use lowercase names for variable 
+
+
+//==================
+val oneTwo = Seq(1, 2, 3).permutations
+
+if (oneTwo.length > 0) {
+  println("Permutations of 1 and 2:")
+  oneTwo.foreach(println)
+}
+
+val threeFour = Seq(3, 4, 5).permutations
+
+if (!threeFour.isEmpty) {
+  println("Permutations of 3 and 4:")
+  threeFour.foreach(println)
+}
 
+//==================
+val (a, b, c) =
+    if (4 < 5) {
+        "bar"
+    } else { 
+        Some(10)
+    }
+
+//Because when an expression has multiple return branches, Scala tries to
+//be helpful, by picking the first common ancestor type of all the
+//branches as the type of the whole expression.
+//
+//In this case, one branch has type String and the other has type
+//Option[Int], so the compiler decides that what the developer really
+//wants is for the whole if/else expression to have type Serializable,
+//since that’s the most specific type to claim both String and Option as
+//descendants.
+//
+//And guess what, Tuple3[A, B, C] is also Serializable, so as far as the
+//compiler is concerned, the assignment of the whole mess to (a, b, c)
+//can’t be proven invalid. So it gets through with a warning,
+//destined to fail at runtime.
+
+
+//================
+// does not work anymore in 2.13.0
+val numbers = List("1", "2").toSet() + "3"
\ No newline at end of file