Advanced Object-Oriented Languages Scala Programming Paradigms Department of Computer Science University of Aalborg, Denmark Erik Ernst (U. of Aarhus)
Overview Purpose of using Scala Introduction syntax Entity concepts: Class, mixin, trait Functional programming Pattern matching Generic classes Variance Summary October 2008, Aalborg 2 Erik Ernst
Purpose of using Scala Overview Purpose of using Scala Introduction syntax Entity concepts: Class, mixin, trait Functional programming Pattern matching Generic classes Variance Summary October 2008, Aalborg 3 Erik Ernst
Purpose of using Scala Why Scala? It is a real programming language, can be used for experiments Much more interesting than, e.g., Java, and cleaner Run-time concepts: class, mixin, trait Type related concepts: type arguments, variance Functional programming in an OO context October 2008, Aalborg 4 Erik Ernst
Introduction syntax Overview Purpose of using Scala Introduction syntax Entity concepts: Class, mixin, trait Functional programming Pattern matching Generic classes Variance Summary October 2008, Aalborg 5 Erik Ernst
Introduction syntax To read Scala, start thinking Java class Point(xc: int, yc: int) { var x: int = xc var y: int = yc def move(dx: int, dy: int): unit = { x = x + dx y = y + dy override def tostring(): String = ( + x +, + y + ) October 2008, Aalborg 6 Erik Ernst
Introduction syntax Constructor arguments are given to the class class Point(xc: int, yc: int) { var x: int = xc var y: int = yc def move(dx: int, dy: int): unit = { x = x + dx y = y + dy override def tostring(): String = ( + x +, + y + ) October 2008, Aalborg 7 Erik Ernst
Introduction syntax Types are specified after names, with a colon class Point(xc: int, yc: int) { var x: int = xc var y: int = yc def move(dx: int, dy: int): unit = { x = x + dx y = y + dy override def tostring(): String = ( + x +, + y + ) October 2008, Aalborg 8 Erik Ernst
Introduction syntax Declarations start with a keyword; def has = class Point(xc: int, yc: int) { var x: int = xc // cf. constant: val x: int = xc var y: int = yc def move(dx: int, dy: int): unit = { x = x + dx y = y + dy override def tostring(): String = ( + x +, + y + ) // override required because of impl. in the superclass October 2008, Aalborg 9 Erik Ernst
Introduction syntax Built-in types have slightly different names class Point(xc: int, yc: int) { var x: int = xc var y: int = yc def move(dx: int, dy: int): unit = { x = x + dx y = y + dy override def tostring(): String = ( + x +, + y + ) October 2008, Aalborg 10 Erik Ernst
Introduction syntax An overview of the built-in types Any AnyVal AnyRef (java.lang.object) Double Int... Unit Boolean... ScalaObject Seq List java.lang.string... (other java classes) Option... (other scala classes) Null Nothing October 2008, Aalborg 11 Erik Ernst
Introduction syntax No special casing for primitive types (cf. Java) object UnifiedTypes extends Application { val set = new scala.collection.mutable.hashset[any]; set += This is a string // add a string set += 732 // add a number set += c // add a character set += true // add a boolean value set += &main // add the main function val iter: Iterator[Any] = set.elements; while (iter.hasnext) { System.out.println(iter.next) October 2008, Aalborg 12 Erik Ernst
Entity concepts: Class, mixin, trait Overview Purpose of using Scala Introduction syntax Entity concepts: Class, mixin, trait Functional programming Pattern matching Generic classes Variance Summary October 2008, Aalborg 13 Erik Ernst
Entity concepts: Class, mixin, trait Classes A class describes a set of objects with similar structure, but different identity and possibly different state Point is a fine example new syntax, but apart from that it is basically recognizable as Java-like code Subclasses also as in Java, with extends, and inheritance too, apart from the requirement to use override (Later we will take a brief look at case-classes) October 2008, Aalborg 14 Erik Ernst
Entity concepts: Class, mixin, trait Mixins with an excursion to Java... Sometimes it is impractical that only subclasses can inherit the implementation of a set of methods Listener I JFrame E.g.: In Java, if I is an interface and C is a class implementing I then one cannot reuse the implementation in C in a class which must get another superclass than C Work-around: Copy the code! MyListener C MyWindow October 2008, Aalborg 15 Erik Ernst
Entity concepts: Class, mixin, trait Mixins with an excursion outside Java... Sometimes it is impractical that only subclasses can inherit the implementation of a set of methods Listener I JFrame E.g.: In Java, if I is an interface and C is a class implementing I then one cannot reuse the implementation in C in a class which must get another superclass than C MyListener MyWindow C Better: let C be a mixin! October 2008, Aalborg 16 Erik Ernst
Entity concepts: Class, mixin, trait Mixin example in Scala setup class Point2D(xc: int, yc: int) { val x = xc val y = yc override def tostring() = x = + x +, y = + y class Point3D(xc: int, yc: int, zc: int) extends Point2D(xc, yc) { val z = zc override def tostring() = super.tostring +, z = + z October 2008, Aalborg 17 Erik Ernst
Entity concepts: Class, mixin, trait Mixin example in Scala def. and apply trait Colored { var color = Black def setcolor(newcol: String): unit = color = newcol override def tostring() = super.tostring +, col = + color class ColoredPoint3D(xc: int, yc: int, zc: int) extends Point3D(xc, yc, zc) with Colored October 2008, Aalborg 18 Erik Ernst
Entity concepts: Class, mixin, trait Mixins in Scala in general Point2D Mixins are declared with trait and used with with A mixin cannot have constructor parameters Compared to Java interfaces, mixins can have implemented methods and state The point is that we can add it to more than one class! Point3D ColoredPoint3D Colored October 2008, Aalborg 19 Erik Ernst
Entity concepts: Class, mixin, trait A remark about general traits The general traits concept is in fact not identical to Scala s traits General traits cannot have state General traits have method renaming/exclusion/selection All in all: general traits are very flexible method tool boxes, and traits in Scala are almost the same thing However, Scala traits are actually closer to another concept, mixins, which is why that term was used October 2008, Aalborg 20 Erik Ernst
Entity concepts: Class, mixin, trait Another trait/mixin example trait HasAdd { def add(i: int, j: int) = i+j def mult(i: int, j: int): int trait HasMult { def add(i: int, j: int): int def mult(i: int, j: int) = i*j abstract class HalfComputer { def start: unit; def stop: unit class Computer extends HalfComputer with HasMult with HasAdd { def start =...; def stop =... October 2008, Aalborg 21 Erik Ernst
Functional programming Overview Purpose of using Scala Introduction syntax Entity concepts: Class, mixin, trait Functional programming Pattern matching Generic classes Variance Summary October 2008, Aalborg 22 Erik Ernst
Functional programming Functional programming = OO with a twist ;-) Functional programming was designed into Scala from the very beginning But it is in fact pure OO made slightly more concise Functions are objects, function calls are method calls, and methods can be turned into functions on demand An element that did not fit in: Lazy evaluation An element that was not needed: Structural type equivalence October 2008, Aalborg 23 Erik Ernst
Functional programming Basic functional programming in Scala // an anonymous function... (x: int) = x + 1... // can, e.g., initialize a variable val f: (Int= Int) = ((x: Int) = x + 1) // what this actually means (we return to this later) val f2 = new Function1[Int, Int] { def apply(x: Int): Int = x + 1 // methods are transformed to functions on demand val s = 5 System.out.println(s.toString +, + s.tostring ) // prints 5, function October 2008, Aalborg 24 Erik Ernst
Functional programming An example of a higher-order function class Decorator(left: String, right: String) { def layout(x: int) = left + x.tostring + right object FunTest extends Application { def app(f: int = String, v: int) = f(v) val decorator = new Decorator( [, ] ) System.out.println(app(decorator.layout, 7)) So what does it print? October 2008, Aalborg 25 Erik Ernst
Functional programming Using block structure (free variables where?) object FilterTest extends Application { def filter(xs: List[int], threshold: int) = { def process(ys: List[int]): List[int] = if (ys.isempty) ys else if (ys.head threshold) ys.head :: process(ys.tail) else process(ys.tail); process(xs) System.out.println(filter(List(1, 9, 2, 8, 3, 7, 4), 5)) October 2008, Aalborg 26 Erik Ernst
Functional programming Using block structure (free variable) object FilterTest extends Application { def filter(xs: List[int], threshold: int) = { def process(ys: List[int]): List[int] = if (ys.isempty) ys else if (ys.head threshold) ys.head :: process(ys.tail) else process(ys.tail); process(xs) System.out.println(filter(List(1, 9, 2, 8, 3, 7, 4), 5)) October 2008, Aalborg 27 Erik Ernst
Functional programming Currying (to receive arguments one by one) object CurryTest extends Application { def filter(xs: List[int], p: int = boolean): List[int] = if (xs.isempty) xs else if (p(xs.head)) xs.head :: filter(xs.tail, p) else filter(xs.tail, p) def modn(n: int)(x: int) = ((x % n) == 0) val nums = List(1, 2, 3, 4, 5, 6, 7, 8) System.out.println(filter(nums, modn(2))) System.out.println(filter(nums, modn(3))) October 2008, Aalborg 28 Erik Ernst
Functional programming Functional programming in Scala Scala does not enforce immutability, but it is eagerly supported Anonymous and higher-order functions are easy to express and use Standardization removes the need for structural type equivalence For infinite structures and similar lazy tricks some manual work is required October 2008, Aalborg 29 Erik Ernst
Pattern matching Overview Purpose of using Scala Introduction syntax Entity concepts: Class, mixin, trait Functional programming Pattern matching Generic classes Variance Summary October 2008, Aalborg 30 Erik Ernst
Pattern matching A special kind of classes are used for pattern matching // Example: lambda terms abstract class Term case class Var(name: String) extends Term case class Fun(arg: String, body: Term) extends Term case class App(f: Term, v: Term) extends Term // We may now create expressions, e.g.... Fun( x, Fun( y, App(Var( x ), Var( y ))))... Note: No need to write new for creation of objects October 2008, Aalborg 31 Erik Ernst
Pattern matching Pattern matching is achieved with match/case def print(term: Term): unit = term match { case Var(n) = System.out.print(n) case Fun(x,b) = System.out.print( ˆ + x +. ); print(b) case App(f,v) = System.out.print( ( ); print(f) System.out.print( ); print(v); System.out.print( ) ) def isidentityfun(term: Term): boolean = term match { case Fun(x, Var(y)) if x == y = true case = false val id = Fun( x, Var( x )) val t = Fun( x, Fun( y, App(Var( x ), Var( y )))) print(t); System.out.println(isIdentityFun(id)) System.out.println(isIdentityFun(t)) October 2008, Aalborg 32 Erik Ernst
Generic classes Overview Purpose of using Scala Introduction syntax Entity concepts: Class, mixin, trait Functional programming Pattern matching Generic classes Variance Summary October 2008, Aalborg 33 Erik Ernst
Generic classes Type arguments We often need to use a class with more than one type in a specific location Typical example: sets, stacks, lists Known solutions: (too) general data type; copying source code; using type arguments October 2008, Aalborg 34 Erik Ernst
Generic classes A general stack (what s the problem?) class GeneralStack { var elems = Nil def push(x: Any): unit = elems = x :: elems def top: Any = elems.head def pop: unit = elems = elems.tail October 2008, Aalborg 35 Erik Ernst
Generic classes The problem is loss of type information! val greeting: String = Hello, world! val mystack: GeneralStack = new GeneralStack mystack.push(greeting) // Type error in the next line!! val samegreeting: String = mystack.top October 2008, Aalborg 36 Erik Ernst
Generic classes Better: copy-paste to adjust the type (OK now?) class IntStack { var elems = Nil def push(x: int): unit = elems = x :: elems def top: int = elems.head def pop: unit = elems = elems.tail class StringStack { var elems = Nil def push(x: String): unit = elems = x :: elems def top: String = elems.head def pop: unit = elems = elems.tail October 2008, Aalborg 37 Erik Ernst
Generic classes Even better: Make the type an argument class Stack[T] { var elems = Nil def push(x: T): unit = elems = x :: elems def top: T = elems.head def pop: unit = elems = elems.tail October 2008, Aalborg 38 Erik Ernst
Generic classes Using the stack object GenericsTest extends Application { val stack: Stack[int] = new Stack[int]; stack.push(1); stack.push( a ); // what s going on here? System.out.println(stack.top); stack.pop; System.out.println(stack.top); October 2008, Aalborg 39 Erik Ernst
Generic classes NB: we ve been cheating elems must have a type class GeneralStack { var elems: List[Any] = Nil... class IntStack { var elems: List[Int] = Nil... class StringStack { var elems: List[String] = Nil... class Stack[T] { var elems: List[T] = Nil... October 2008, Aalborg 40 Erik Ernst
Variance Overview Purpose of using Scala Introduction syntax Entity concepts: Class, mixin, trait Functional programming Pattern matching Generic classes Variance Summary October 2008, Aalborg 41 Erik Ernst
Variance Variance Subtype example: Point3D is a subtype of Point2D General rule: where a type is expected any subtype can be used, so we can use a Point3D where Point2D is expected Variance is about the rules for when a type is a subtype of an other one First we look at a problem in connection with variance... October 2008, Aalborg 42 Erik Ernst
Variance A typing problem... // int is a subtype of Any, i.e., int : Any val myintstack: Stack[int] = new Stack[int] val mystack: Stack[Any] = myintstack mystack.push( A string, no less ) // what s wrong here? why does it fail? October 2008, Aalborg 43 Erik Ernst
Variance The typing problem...... is not Scala s: the program is rejected with an error message... is based on the assumption that Stack[Int] <: Stack[Any] In general it is caused by the assumption that type functions are increasing: if S <: T, then C[S] <: C[T ] this is often written as follows: Γ S <: T Γ C[S] <: C[T ] and that assumption is wrong!! October 2008, Aalborg 44 Erik Ernst
Variance Covariance fixes the problem Covariant type-args declared using + : class Stack[+T] {... Define covariant positions: Method result types, val types (intuition: where you can get data from something); contravariant positions: Method arguments (put); non-variant positions: var types (both get and put) Limitation: A covariant type-arg can only be used in a covariant position Achieved benefit: Γ S <: T Γ C[S] <: C[T ] if C s type-arg is covariant E.g., Stack[Int] is a subtype of Stack[Any], if the type-arg of Stack is covariant October 2008, Aalborg 45 Erik Ernst
Variance But this type-arg cannot be covariant! class Stack[+T] { var elems: List[T] = Nil // Wonder if this is OK? def push(x: T): unit = elems = x :: elems // ERROR! def top: T = elems.head // OK def pop: unit = elems = elems.tail October 2008, Aalborg 46 Erik Ernst
Variance We must change Stack to use covariance Idea: create a functional version of Stack I.e., never change a stack, always create a similar one and return it Background: we can avoid using the type-arg outside covariant positions if we are willing to pay for it! October 2008, Aalborg 47 Erik Ernst
Variance A functional, covariant stack class Stack[+A] { def push[b : A](elem: B): Stack[B] = new Stack[B] { override def top: B = elem override def pop: Stack[B] = Stack.this override def tostring() = elem.tostring() + + Stack.this.toString() def top: A = error( no element on stack ) def pop: Stack[A] = error( no element on stack ) override def tostring() = October 2008, Aalborg 48 Erik Ernst
Variance Using the functional, covariant stack object VariancesTest extends Application { var s: Stack[Any] = new Stack[Int]().push[String]( hello ) s = s.push(new Object()) s = s.push(7) System.out.println(s) Challenge: which green elements can we have/delete? Why? October 2008, Aalborg 49 Erik Ernst
Variance Summary about the functional, covariant stack It is possible to create a covariant stack! Every time something is push ed on it, it potentially loses type information A useful rule of thumb: This kind of data structure must be used under an explicit type October 2008, Aalborg 50 Erik Ernst
Variance One last variance example: Functions Functions in Scala are represented as objects Function declarations, types, and applications are just syntactic sugar Expansion reduces these mechanisms to ordinary OO mechanisms trait Function[+r] { def apply: r trait Function1[-t,+r] { def apply(x: t): r T 1,..., T k = S just means Functionk[T 1,..., T k, S] f (x) just means f.apply(x) when f is an object October 2008, Aalborg 51 Erik Ernst
Variance Function desugaring example val plus1: (int = int) = (x: int) = x + 1 plus1(2) // is expanded into the following object code: val plus1: Function1[int, int] = new Function1[int, int] { def apply(x: int): int = x + 1 plus1.apply(2) October 2008, Aalborg 52 Erik Ernst
Summary Overview Purpose of using Scala Introduction syntax Entity concepts: Class, mixin, trait Functional programming Pattern matching Generic classes Variance Summary October 2008, Aalborg 53 Erik Ernst
Summary Summary Scala was introduced, and used to illustrate the following concepts: Classes, mixins/traits Type arguments Variance Functions as objects Several connections to earlier lectures: Functions are first-class, higher-order values (though objects) Pattern matching can be used on case-classes (but not: lazy evaluation, ref. transparency) Scala is an example of a multi-paradigmatic language October 2008, Aalborg 54 Erik Ernst