// 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.toString)+ −
+ −
+ −
// 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+ −
+ −
ls.minBy(_._2)+ −
ls.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)+ −
}+ −
} + −
+ −
// distinctBy with the identity function is + −
// just distinct+ −
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[Int](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"+ −
+ −
+ −
+ −
+ −
//+ −
// 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+ −
+ −
Bird("Sparrow")+ −
+ −
println(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 + −
+ −
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)+ −
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+ −
+ −
// not sure if one can get this to work+ −
// properly, since Scala just cannot find out+ −
// if / is for ints or for Fractions + −
(1 / 3) + half+ −
(1 / 2) + third+ −
+ −
// either you have to force the Fraction-type by+ −
// using a method that is not defined for ints+ −
(1 /% 3) + half+ −
(1 /% 2) + third+ −
+ −
+ −
// ...or explicitly give the type in order to allow+ −
// Scala to do the conversion to Fractions + −
((1:Fraction) / 3) + half+ −
(1 / (3: Fraction)) + half+ −
+ −
+ −
+ −
// 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+ −
+ −
// another DFA 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+ −
+ −
// we could also have a dfa for numbers+ −
val sigmai : (S, Int) => S = + −
{ case (S0, 1) => S1+ −
case (S1, 1) => S2+ −
case _ => Sink+ −
}+ −
+ −
val dfa3 = DFA(S0, sigmai, Set[S](S2))+ −
+ −
dfa3.accepts(List(1, 1)) // true+ −
dfa3.accepts(Nil) // false+ −
dfa3.accepts(List(1, 2)) // 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(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+ −
+ −
+ −
// Abstract idea:+ −
// In that version implicit conversions were used to solve the + −
// late extension problem; namely, given a class C and a class T, + −
// how to have C extend T without touching or recompiling C. + −
// Conversions add a wrapper when a member of T is requested + −
// from an instance of C.+ −
+ −
//Another example+ −
+ −
case class Duration(time: Long, unit: TimeUnit) {+ −
def +(o: Duration) = Duration(time + unit.convert(o.time, o.unit), unit)+ −
}+ −
+ −
implicit class Int2Duration(that: Int) {+ −
def seconds = new Duration(that, SECONDS)+ −
def minutes = new Duration(that, MINUTES)+ −
}+ −
+ −
5.seconds + 2.minutes //Duration(125L, SECONDS )+ −
+ −
+ −
+ −
// 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*+ −
+ −
+ −
+ −
// writing (ab)* in the format above is + −
// tedious+ −
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:+ −
// this uses the => in front of the type+ −
// of the code-argument+ −
+ −
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)+ −
("a" * 50000).matches(evil)+ −
+ −
time_needed(1, ("a" * 50000).matches(evil))+ −