progs/lecture5.scala
author Christian Urban <urbanc@in.tum.de>
Tue, 03 Dec 2019 01:22:16 +0000
changeset 326 e5453add7df6
parent 247 50a3b874008a
child 328 0e591f806290
permissions -rw-r--r--
updated

// Scala Lecture 5
//=================



// Laziness with style
//=====================

// The concept of lazy evaluation doesn’t really 
// 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".

// In contrast, say we have a pretty expensive operation:

def peop(n: BigInt): Boolean = peop(n + 1) 

val a = "foo"
val b = "foo"

if (a == b || peop(0)) println("true") else println("false")

// This is called "lazy evaluation":
// you delay compuation until it is really 
// needed. Once calculated though, the result
// does not need to be re-calculated.

// A useful example is
def time_needed[T](i: Int, code: => T) = {
  val start = System.nanoTime()
  for (j <- 1 to i) code
  val end = System.nanoTime()
  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 ....

def generatePrimes (s: LazyList[Int]): LazyList[Int] =
  s.head #:: generatePrimes(s.tail.filter(_ % s.head != 0))

val primes = generatePrimes(LazyList.from(2))

// the first 10 primes
primes.take(10).toList

time_needed(1, primes.filter(_ > 100).take(3000).toList)
time_needed(1, primes.filter(_ > 100).take(3000).toList)

// A Stream (LazyList) of successive numbers:

LazyList.from(2).take(10)
LazyList.from(2).take(10).force

// 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)
fibIter(1, 1).drop(10000).take(1).force


// LazyLists are good for testing


// Regular expressions - the power of DSLs in Scala
//                                     and Laziness
//==================================================

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*


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

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


def depth(r: Rexp) : Int = r match {
  case ZERO => 0
  case ONE => 0
  case CHAR(_) => 0
  case ALT(r1, r2) => Math.max(depth(r1), depth(r2)) + 1
  case SEQ(r1, r2) => Math.max(depth(r1), depth(r2)) + 1 
  case STAR(r1) => depth(r1) + 1
}

//example regular expressions
val digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
val sign = "+" | "-" | ""
val number = sign ~ digit ~ digit.% 

// Task: enumerate exhaustively regular expressions
// starting from small ones towards bigger ones.

// 1st idea: enumerate them all in a Set
// up to a level

def enuml(l: Int, s: String) : Set[Rexp] = l match {
  case 0 => Set(ZERO, ONE) ++ s.map(CHAR).toSet
  case n =>  
    val rs = enuml(n - 1, s)
    rs ++
    (for (r1 <- rs; r2 <- rs) yield ALT(r1, r2)) ++
    (for (r1 <- rs; r2 <- rs) yield SEQ(r1, r2)) ++
    (for (r1 <- rs) yield STAR(r1))
}

enuml(1, "a")
enuml(1, "a").size
enuml(2, "a").size
enuml(3, "a").size // out of heap space



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(LazyList(ZERO, ONE, CHAR('a'), CHAR('b'))).take(200).force
enum(LazyList(ZERO, ONE, CHAR('a'), CHAR('b'))).take(5000000)


val is = 
  (enum(LazyList(ZERO, ONE, CHAR('a'), CHAR('b')))
    .dropWhile(depth(_) < 3)
    .take(10).foreach(println))


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

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



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

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


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
}

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)


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


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


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

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


// 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. Here the state type for the DFA is
//    sets of states.

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



// The End ... Almost Christmas
//===============================

// I hope you had fun!

// A function should do one thing, and only one thing.

// Make your variables immutable, unless there's a good 
// reason not to. Usually there is not.

// I did it once, but this is actually not a good reason:
// generating new labels:

var counter = -1

def Fresh(x: String) = {
  counter += 1
  x ++ "_" ++ counter.toString()
}

Fresh("x")
Fresh("x")



// 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 cup")


// I like best about Scala that it lets me often write
// concise, readable code. And it hooks up with the 
// 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"