diff -r ca9c1cf929fa -r e5453add7df6 progs/lecture5.scala --- 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