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