O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.
Scala programming
Roberto Casadei
December 10, 2015
R. Casadei Scala December 10, 2015 1 / 192
About these notes
I am a learner, not an expert
These notes are essentially a work of synthesis and integration from many ...
To-do
Here a list of some things to look for / read / implement
The expression (extensibility) problem
R. Casadei Scala De...
Outline
1 Basic Scala programming
Basics
Collections
OOP in Scala
Advanced features
Programming techniques
Practical usage...
Basic Scala programming
Outline
1 Basic Scala programming
Basics
Collections
OOP in Scala
Advanced features
Programming te...
Basic Scala programming Basics
Outline
1 Basic Scala programming
Basics
Collections
OOP in Scala
Advanced features
Program...
Basic Scala programming Basics
Summary I
Scala: main characteristics
Smooth integration of OOP and FP
Designed to express ...
Basic Scala programming Basics
Advices
Learn to use the REPL (kinda experiment-driven development)
Think in expressions
St...
Basic Scala programming Basics
Scala REPL
:cp tools/junit.jar ⇒ adds a JAR file to classpath for the Scala interpreter
:loa...
Basic Scala programming Basics
Scala type hierarchy I
Unlike Java, there is no distinction between primitive types and cla...
Basic Scala programming Basics
The very basics I
Declaring values and variables
1 /* Values declared with ’val’ are consta...
Basic Scala programming Basics
The very basics II
Miscellaneous
A block {} contains a set of expressions; its return value...
Basic Scala programming Basics
Programs and delayed init
Similarly to Java, you can define a main method
1 object MyApp {
2...
Basic Scala programming Basics
Case classes
Case classes are regular classes which export their constructor parameters and...
Basic Scala programming Basics
Pattern matching
If no pattern matches, a MatchError is thrown; use the catch-all case _ pa...
Basic Scala programming Basics
For comprehension I
In for loops, you can have multiple generators (separated by semicolons...
Basic Scala programming Basics
Functions I
Basics: function definition, function types, lambdas, partial function applicati...
Basic Scala programming Basics
Functions II
One nice thing of functions being traits is that we can subclass the function ...
Basic Scala programming Basics
Methods with multiple parameter lists
Methods may define multiple parameter lists.
All the p...
Basic Scala programming Basics
Partial functions: trait PartialFunction[-A,+B]
NOTE: partial functions ARE NOT partially a...
Basic Scala programming Basics
Curried functions
Currying is the conversion of a function of multiple parameters into a ch...
Basic Scala programming Basics
Parameters: default args, named args
1 // DEFAULT ARGUMENTS
2 def f(x: Int, mul: Int, dec: ...
Basic Scala programming Basics
Control abstractions
You can model a seq of statements as a functions with no params or ret...
Basic Scala programming Basics
Operators I
Unary and binary operators are method calls
Infix operators. a op b where op is ...
Basic Scala programming Basics
Operators II
apply method: Scala allows you to use the function call syntax to values other...
Basic Scala programming Basics
Extractors I
An extract is an object with an unapply method, which takes an object and extr...
Basic Scala programming Basics
Extractors II
1 object Name {
2 def unapplySeq(input: String): Option[Seq[String]] =
3 if (...
Basic Scala programming Basics
Exceptions
throw expressions have the special type Nothing. That is useful in if/else expre...
Basic Scala programming Basics
Option I
1 sealed trait Option[+A]
2 case class Some[+A](get: A) extends Option[A]
3 case o...
Basic Scala programming Basics
Option II
1 Option(1).toList // => List(1)
2 Option(1) foreach print // 1
3 List(1,2) ++ So...
Basic Scala programming Basics
Either[T]
1 sealed trait Either[+E, +A]
2 case class Left[+E](value: E) extends Either[E, N...
Basic Scala programming Basics
Annotations I
Annotations are tags that you insert in the source code so that some tools (o...
Basic Scala programming Basics
Annotations II
StaticAnnotations are available to the Scala type checker and are visible ac...
Basic Scala programming Basics
Annotations III
If you call a Scala method from Java code, its signature should include the...
Basic Scala programming Basics
Annotations IV
Use @deprecated(message=“...”) to mark deprecated features and generate warn...
Basic Scala programming Basics
Functions vs. methods I
Reference: http://stackoverflow.com/a/2530007/2250712
According to ...
Basic Scala programming Basics
Functions vs. methods II
Remember, FunctionN trait defines an abstract method apply(v1:T1,.....
Basic Scala programming Collections
Outline
1 Basic Scala programming
Basics
Collections
OOP in Scala
Advanced features
Pr...
Basic Scala programming Collections
Basics of collections in Scala I
All collections extend the Iterable trait
The 3 major...
Basic Scala programming Collections
Basics of collections in Scala II
Scala’s collections split into 3 dichotomies
1 Immut...
Basic Scala programming Collections
Basics of collections in Scala III
Mutable and immutable collections
Scala collections...
Basic Scala programming Collections
scala.collection
These are all high-level abstract classes or traits, which generally ...
Basic Scala programming Collections
Generic collections
The collections hierarchy starts with the trait TraversableOnce, w...
Basic Scala programming Collections
Traversable[+T]
It implements the behavior common to all collections, in terms of the ...
Basic Scala programming Collections
Iterable[+T]
Internal vs. external iterators
Internal iterator (supported through Trav...
Basic Scala programming Collections
Seq[+T], LinearSeq[+T], IndexedSeq[+T]
Seq represents collections that have sequential...
Basic Scala programming Collections
Set[T]
Set denotes a collection where each element is unique, at least according to th...
Basic Scala programming Collections
scala.collection.Map[K,V]
Map denotes a collection of key value pairs where only one v...
Basic Scala programming Collections
Tuples
Type (T1,T2,T3) === Tuple3[T1,T2,T3]
1 val c1 = (’a’)
2 val t1 = Tuple1(’a’)
3 ...
Basic Scala programming Collections
Some notes on collection usage
Immutable collections
In general, use Vector
When frequ...
Basic Scala programming Collections
scala.collection.immutable
R. Casadei Scala December 10, 2015 51 / 192
Basic Scala programming Collections
Vector I
Vector is a general-purpose, immutable data structure which provides random a...
Basic Scala programming Collections
Vector II
Trie (aka prefix tree)
A trie is a tree where every child in a given path dow...
Basic Scala programming Collections
scala.collection.immutable.List
This class is optimal for last-in-first-out (LIFO), sta...
Basic Scala programming Collections
Stream
A stream is an immutable list in which the tail is computed lazily
It can repre...
Basic Scala programming Collections
scala.collection.mutable
R. Casadei Scala December 10, 2015 56 / 192
Basic Scala programming Collections
scala.Array[T]
Arrays are mutable, indexed collections of values
Predef provides addit...
Basic Scala programming Collections
scala.collection.mutable.ArrayBuffer[T]
Buffers are used to create sequences of elemen...
Basic Scala programming Collections
Mutation event publishing
When ObservableMap, ObservableSet or ObservableBuffer are mi...
Basic Scala programming Collections
Lazy views
Calling view on a collection yields a collection on which methods are appli...
Basic Scala programming Collections
Summary of operators for adding/removing elems from colls I
Prepend/Append (Seq)
coll ...
Basic Scala programming Collections
Summary of operators for adding/removing elems from colls II
coll -= (e1,e2,..)
coll -...
Basic Scala programming Collections
Common methods I
Important methods of the Iterable trait
head, last, headOption, lastO...
Basic Scala programming Collections
Common methods II
mkString(before, between, after), addString(stringBuilder, before, b...
Basic Scala programming Collections
Thread-safe collections
The scala library provides 6 traits you can miz in with collec...
Basic Scala programming Collections
Parallel collections
coll.par will produce a parallel implementation of the collection...
Basic Scala programming Collections
Scala collections: performance characteristics
R. Casadei Scala December 10, 2015 67 /...
Basic Scala programming OOP in Scala
Outline
1 Basic Scala programming
Basics
Collections
OOP in Scala
Advanced features
P...
Basic Scala programming OOP in Scala
Classes: fields I
Fields (declared with var or val) in classes automatically come with...
Basic Scala programming OOP in Scala
Constructors
Every class has a primary constructor that consists in the class body (i...
Basic Scala programming OOP in Scala
Nested classes
In Scala, you can nest just about anything inside anything. You can de...
Basic Scala programming OOP in Scala
Access modifiers
As in Java, you have the public/private/protected access modifiers
How...
Basic Scala programming OOP in Scala
Objects
The object keyword create a new singleton type, i.e., a type with only one va...
Basic Scala programming OOP in Scala
Enumerations
1 object TrafficLightColor extends Enumeration {
2 val Red, Yellow, Gree...
Basic Scala programming OOP in Scala
Object equality
equals and hashCode are defined in Any (parent of AnyRef and AnyVal) a...
Basic Scala programming OOP in Scala
Polymorphic equality I
In general, it’s best to avoid polymorphism with types requiri...
Basic Scala programming OOP in Scala
Polymorphic equality II
1 class A (val a: Int) extends Equals {
2 override def canEqu...
Basic Scala programming OOP in Scala
Packages and imports I
A package can contain classes, objects, and traits, but not th...
Basic Scala programming OOP in Scala
Packages and imports II
Package paths are relative, not absolute
1 package com {
2 pa...
Basic Scala programming OOP in Scala
Packages and imports III
1 import java.awt._ // Import all members of a package
2 imp...
Basic Scala programming OOP in Scala
Inheritance
The extends and final keywords are as in Java
sealed means that the class...
Basic Scala programming OOP in Scala
Scala inheritance hierarchy
The Any class is at the top.
AnyVal (extends Any) is the ...
Basic Scala programming OOP in Scala
Type checks and casts
If p is null, then p.isInstanceOf[T] returns false, and p.asIns...
Basic Scala programming OOP in Scala
On visibility I
Access modifiers are more sophisticated as in Java.
You can restrict v...
Basic Scala programming OOP in Scala
On visibility II
1 // private (must fail when accessed in subclass)
2 class X(private...
Basic Scala programming OOP in Scala
On visibility III
1 package a {
2 class X(private[a] val x: Int)
3 class Y(protected[...
Basic Scala programming OOP in Scala
On visibility IV
1 package c { }
2 package a {
3 package b {
4 private class X
5 priv...
Basic Scala programming OOP in Scala
On visibility V
20 class SubX extends X {
21 class SubZ extends Z(7,8) { this.z1 + th...
Basic Scala programming OOP in Scala
Traits I
A trait is a special form of an abstract class which does not have any param...
Basic Scala programming OOP in Scala
Traits II
1 trait Logger {
2 def log(msg: String) // Abstract method
3
4 def info(msg...
Basic Scala programming OOP in Scala
Traits III
A trait can also extend a class. That class becomes a superclass of any cl...
Basic Scala programming OOP in Scala
Construction order I
Construction order
1 Superclass constructor
2 Trait constructors...
Basic Scala programming OOP in Scala
Construction order II
1
2 lin(B) = B >> lin(S) >> lin(T) >> lin(A)
3 = B >> (S >> H) ...
Basic Scala programming OOP in Scala
Initializing fields and early definitions I
Traits cannot have constructor params: ever...
Basic Scala programming OOP in Scala
Initializing fields and early definitions II
1 trait FileLogger extends Logger {
2 val ...
Basic Scala programming OOP in Scala
Self types
When a trait starts out with this: AType => then it can only be mixed into...
Basic Scala programming OOP in Scala
What happens under the hood with traits
Scala needs to translate traits into classes ...
Basic Scala programming OOP in Scala
Value classes
Value classes are subclasses of AnyVal. They provide a way to improve p...
Basic Scala programming OOP in Scala
OOD - Rules of thumbs I
Rule 1) Avoid abstract val in traits – Using abstract values ...
Basic Scala programming OOP in Scala
OOD - Rules of thumbs II
Rule 2) Provide empty implementations for abstract methods o...
Basic Scala programming OOP in Scala
OOD - Rules of thumbs III
1 // B defines the abstract behavior; C provides the defaul...
Basic Scala programming OOP in Scala
OOD - Rules of thumbs IV
Rule 3) Promote abstract interface into its own trait – It’s...
Basic Scala programming OOP in Scala
Composition and inheritance in Scala I
Some terminology
Inheritance-composition: comp...
Basic Scala programming OOP in Scala
Composition and inheritance in Scala II
1 trait Logger { def log(s: String) = println...
Basic Scala programming OOP in Scala
Composition and inheritance in Scala III
1 def ‘...‘: Unit = {} // To make this examp...
Basic Scala programming Advanced features
Outline
1 Basic Scala programming
Basics
Collections
OOP in Scala
Advanced featu...
Basic Scala programming Advanced features
On Scala’s Type System I
In general, a type system enables lots of rich optimiza...
Basic Scala programming Advanced features
On Scala’s Type System II
1 class C; trait T; object O
2
3 def c(c: C) = c
4 def...
Basic Scala programming Advanced features
On Scala’s Type System III
Stable members are packages, objects, or value definit...
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Programming in Scala: Notes
Próximos SlideShares
Carregando em…5
×

Programming in Scala: Notes

1.495 visualizações

Publicada em

Some notes about programming in Scala: it covers Scala syntax and semantics, programming techniques, idioms, patterns. Many Scala features are introduced, from basic to intermediate and advanced. These are not introductory notes, but they assume a working knowledge with some other programming language (Java, C#, C++), object-oriented programming (OOP) concepts, and functional programming (FP) concepts.

Publicada em: Tecnologia
  • Seja o primeiro a comentar

Programming in Scala: Notes

  1. 1. Scala programming Roberto Casadei December 10, 2015 R. Casadei Scala December 10, 2015 1 / 192
  2. 2. About these notes I am a learner, not an expert These notes are essentially a work of synthesis and integration from many sources, such as “Scala for the Impatient” [Horstmann, 2012] “Scala in Action” [Raychaudhuri, 2013] “Scala in Depth” [Suereth, 2012] “Functional Programming in Scala” [Chiusano and Bjarnason, 2014] University notes Web sources: Wikipedia, Blogs, etc. (references in slides) Scientific articles R. Casadei Scala December 10, 2015 2 / 192
  3. 3. To-do Here a list of some things to look for / read / implement The expression (extensibility) problem R. Casadei Scala December 10, 2015 3 / 192
  4. 4. Outline 1 Basic Scala programming Basics Collections OOP in Scala Advanced features Programming techniques Practical usage Internal DSL implementation in Scala 2 Articles Scalable Component Abstractions R. Casadei Scala December 10, 2015 4 / 192
  5. 5. Basic Scala programming Outline 1 Basic Scala programming Basics Collections OOP in Scala Advanced features Programming techniques Practical usage Internal DSL implementation in Scala 2 Articles Scalable Component Abstractions R. Casadei Scala December 10, 2015 5 / 192
  6. 6. Basic Scala programming Basics Outline 1 Basic Scala programming Basics Collections OOP in Scala Advanced features Programming techniques Practical usage Internal DSL implementation in Scala 2 Articles Scalable Component Abstractions R. Casadei Scala December 10, 2015 6 / 192
  7. 7. Basic Scala programming Basics Summary I Scala: main characteristics Smooth integration of OOP and FP Designed to express common programming patterns in concise/typesafe way Runs on JVM and .NET (not very stable – currently → IKVM) Pure OOPL Everything is an object All operations are messages to objects R. Casadei Scala December 10, 2015 7 / 192
  8. 8. Basic Scala programming Basics Advices Learn to use the REPL (kinda experiment-driven development) Think in expressions Statement vs. expression: a statement is something that executes; an expression is something that evaluates to a value. Don’t use return. In Scala, some control blocks (if, match, ..) are also expressions. Prefer immutability Use None instead of null (cf. Option type) R. Casadei Scala December 10, 2015 8 / 192
  9. 9. Basic Scala programming Basics Scala REPL :cp tools/junit.jar ⇒ adds a JAR file to classpath for the Scala interpreter :load myfile.scala :quit :type expr ⇒ gives the type of expr without evaluating it The Scala REPL attempts to parse input as soon as it possibly can. Use :paste to enter in paste mode, which allows you to compile many code blocks at once (so that you can, e.g., define companion objects) R. Casadei Scala December 10, 2015 9 / 192
  10. 10. Basic Scala programming Basics Scala type hierarchy I Unlike Java, there is no distinction between primitive types and class types in Scala R. Casadei Scala December 10, 2015 10 / 192
  11. 11. Basic Scala programming Basics The very basics I Declaring values and variables 1 /* Values declared with ’val’ are constants */ 2 val x: Double = 2 // Type explicitly provided 3 val y = 3 // Type inferred 4 y = 7 // Error: cannot change a val 5 val a,b,c: List[Any] = Nil // All a,b,c are List[Any] and assigned the empty list 6 7 /* Variables */ 8 var m, n: Int = 10 9 m = m+n // Vars can be changed, here m = 20 Conditional expressions 1 // If/Else expressions yields values 2 > val s = if(false) 1 else -1 // s = -1 3 > :type if(true) 1 else "ciao" // Any (as it’s supertype of java.lang.String and Int) 4 > :type if(0==0) ’a’ else throw new Exception() // Char (note: throw yields Nothing) R. Casadei Scala December 10, 2015 11 / 192
  12. 12. Basic Scala programming Basics The very basics II Miscellaneous A block {} contains a set of expressions; its return value is the value of its last expression Assignments evaluate to Unit; so you cannot chain assignments together When a val is declared lazy, its initialization is deferred until it is accessed for the first time 1 { } == () // => true 2 repl> :type () // Unit 3 4 repl> :type { val x = 10 } // Unit 5 6 var y = 10 7 lazy val x = y+1 8 y = 20 9 println(x) // 21 Basic I/O 1 println("Count up to " + 100) 2 printf("Hello %s, I am %d years old", "man", 25) 3 val name = readLine("What is your name?") 4 val radius = readDouble() R. Casadei Scala December 10, 2015 12 / 192
  13. 13. Basic Scala programming Basics Programs and delayed init Similarly to Java, you can define a main method 1 object MyApp { 2 def main(args: Array[String]): Unit = { /* ... */ } 3 } Alternatively, the App trait can be used to quickly turn objects into executable programs 1 object Main extends App { 2 Console.println("Hello World: " + (args mkString ", ")) 3 } App extends DelayedInit, a trait that defines a single method delayedInit 1 trait DelayedInit { 2 def delayedInit(x: => Unit): Unit // Note the lazy argument 3 } Classes and objects (but note, not traits) inheriting the DelayedInit marker trait will have their initialization code rewritten as follows: code becomes delayedInit(code) 1 trait MyApp extends DelayedInit { 2 override def delayedInit(body: => Unit) { 3 print("bbb") 4 body 5 } 6 } 7 val p = new MyApp { print("bbb") } // Will print: aaabbb DelayedInit trait solves the problem where construction and initialization of objects are required to happen at different times R. Casadei Scala December 10, 2015 13 / 192
  14. 14. Basic Scala programming Basics Case classes Case classes are regular classes which export their constructor parameters and which provide a recursive decomposition mechanism via pattern matching. 1 // This class hierarchy can be used to represent terms of the untyped lambda calculus 2 abstract class Term 3 case class Var(name: String) extends Term 4 case class Fun(arg: String, body: Term) extends Term 5 case class App(f: Term, v: Term) extends Term 6 7 // Usage 8 val f = Fun("x", Fun("y", App(Var("x"), Var("y")))) 9 Console.println(f.body) // => Fun("y", App(Var("x"), Var("y"))) 10 f == Fun("x", f.body) // => true 11 f match { 12 case Var(x) => /* ... */ 13 case Fun(a,b) => /* ... */ 14 /* ... */ 15 } 16 17 // Defining an Algebraic Data Type (in simplest form: a enumerated type) 18 sealed abstract class Bool // Introduce the type 19 case object True extends Bool // Value constructor 20 case object False extends Bool // Value constructor Case classes can be seen as plain and immutable data-holding objects that should exclusively depend on their constructor arguments They can be used to define algebraic datatypes (i.e., types whose values are generated by an algebra – the constructors) No need to use new for instantiation (apply method automatically def in companion object) Constructor params are publicly accessible (they become a val) equals (it impls structural equality), toString, hashCode and copy are generated unapply is automatically provided so that you can use pattern matching to decompose data structures R. Casadei Scala December 10, 2015 14 / 192
  15. 15. Basic Scala programming Basics Pattern matching If no pattern matches, a MatchError is thrown; use the catch-all case _ pattern to avoid that A pattern can include an arbitrary condition (guard), introduced with if You can match on the type of an expression You can match patterns of arrays/tuples/case classes, and bind parts of the pattern to variables 1 obj match { 2 case x: Int if x<=0 => 0 3 case x: Int if x>0 => x 4 case s: String => Integer.parseInt(s) 5 case _ => 0 6 } 7 8 lst match { 9 case x :: y :: Nil => x + " " + y 10 case 0 :: tail => "0 ..." 11 } 12 arr match { // The Array companion object is an extractor => Array.unapplySeq(arr) 13 case Array(x, y) => x + " " + y 14 case whole Array(0, rest _*) => "0 ..." 15 } 16 tpl match { 17 case (x, y) => x + " " + y 18 } R. Casadei Scala December 10, 2015 15 / 192
  16. 16. Basic Scala programming Basics For comprehension I In for loops, you can have multiple generators (separated by semicolons) in the form var <- expr Each generator can have a guard, a boolean condition preceded by if You can also have any number of variable definitions For comprehension: when the body of the for loop started with yield, then the loop constructs a collection of values, one for each iteration 1 for(i <- 1 to 4 if i%2==0; from = 4-i; j <- from to 3) 2 print("(i=" + i + "; j=" + j + ")..") 3 // (i=2; j=2)..(i=2; j=3)..(i=4; j=0)..(i=4; j=1)..(i=4; j=2)..(i=4; j=3).. 4 5 /* The generated collection is compatible with the first generator */ 6 scala> for(c <- "Hello"; i<- 0 to 1) yield (c+i).toChar // => String = HIeflmlmop 7 scala> for(i <- 0 to 1; c<- "Hello") yield (c+i).toChar // => Vector(H, e, l, l, o, I, f, m, m, p) The Scala compiler expresses for-expressions in terms of map, flatMap and a lazy variant of filter. 1 for (x <- e1) yield e2 === e1.map(x => e2) 2 for (x <- e1 if f; s) yield e2 === for (x <- e1.withFilter(x => f); s) yield e2 3 for (x <- e1; y <- e2; s) yield e3 === e1.flatMap(x => for (y <- e2; s) yield e3) 4 // Translation of pattern matching in for (p is a pattern with a single var x) 5 for (p <- e) yield === 6 x <- expr withFilter { case p => true; case _ => false } map { case p => x } 7 8 // Example 9 for { i <- 1 until n; 10 j <- 1 until i if isPrime(i + j) 11 } yield (i, j) 12 // The previous is equal to 13 (1 until n).flatMap(i => 14 (1 until i).withFilter(j => isPrime(i+j)) 15 .map(j => (i, j))) R. Casadei Scala December 10, 2015 16 / 192
  17. 17. Basic Scala programming Basics Functions I Basics: function definition, function types, lambdas, partial function application 1 /* FUNCTION DEFINITION */ 2 def sum1(a:Int, b:Int) { a + b } // Return type is Unit (i.e., it is a PROCEDURE) 3 def sum2(a:Int, b:Int):Int { a+b } // Explicit return type 4 def sum3(a:Int, b:Int) = a+b // The ’=’ activates type inference 5 6 /* FUNCTION TYPES */ 7 repl> :type sum1 // (Int,Int) => Int === Function2[Int,Int,Int] 8 repl> :type () => println("") // () => Unit === Function0[Unit] 9 repl> :type (a:Int) => (b:Double) => a+b // Int => (Double => Double) === Function1[Int,Function1[ Double,Double]] 10 11 /* LAMBDAS */ 12 val f1 = (a:Double, b:Double) => a>b 13 val f2 = () => println("hello") 14 val f3 = (a:Int) => (b:Int) => (c:Int) => a+b+c 15 val f4: (Int,Int)=>Int = _+_ 16 17 /* PARTIALLY APPLIED FUNCTIONS */ 18 val psum = sum1(_:Int, 10) 19 val psum2 = sum1(20, _:Int) 20 val multiArgF = (a:Int) => (b:Int,c:Int) => a*(b+c) 21 val psum3 = multiArgF(_:Int)(7, _:Int) 22 psum3(2, 3) // => 20 23 24 /* CLOSURE (In the body of a function, you can access any var from an enclosing scope) */ 25 def mulBy(factor: Double) = (x: Double) => factor * x 26 27 /* FROM METHOD TO FUNCTION */ 28 import scala.math._ 29 5.33 ceil // => 6 30 val myf = ceil _ // Turn the ceil method into a function A function type such as A => B is a shorthand for scala.Function1[A,B] 1 trait Function1[-A, +R]{ 2 def apply(x: A): R 3 } R. Casadei Scala December 10, 2015 17 / 192
  18. 18. Basic Scala programming Basics Functions II One nice thing of functions being traits is that we can subclass the function type 1 trait Map[Key, Value] extends (Key => Value) ... // Maps are functions of their keys 2 trait Seq[Elem] extends (Int => Elem) ... // Similarly, seqs are funs of their indexes A monomorphic function operate on only one type of data A polimorphic function accepts type parameters to abstract over the types it deals with Functions can be composed via f1 compose f2 or f1 andThen f2 To partially apply a function, you have to use the placeholder _ for all parameters not bound to an argument value, and you must also specify their types _ is also used as a shorthand for lambdas, e.g., _+_ in place of (a,b)=>a+b. This can be used only when the types of the args can be inferred. Each underscore in an anonymous function expression introduces a new (unnamed) function parameter and references it (in left-to-right order). In Scala there is a rather arbitrary distinction between functions defined as methods, which are introduced with the def keyword, and function values, which are first-class objects. There are cases when Scala lets us pretend the distinction doesn’t exist. In other cases, you’ll be forced to write f _ to convert a def to a function value. In Scala, you cannot manipulate methods, only functions (you can use _ to turn a method into a function) R. Casadei Scala December 10, 2015 18 / 192
  19. 19. Basic Scala programming Basics Methods with multiple parameter lists Methods may define multiple parameter lists. All the parameter lists must be provided on function call; but you may 1 def multiParamSum(a:Int,b:Int)(c:Int) = a+b+c // multiParamSum: (a: Int)(b: Int)(c: Int)Int 2 val q = multiParamSum(10,20)(30) // 60 3 val w = multiParamSum(10,20,30) // ERROR: too many arguments 4 val e = multiParamSum(10) // ERROR: not enough arguments 5 val r = multiParamSum(10,20) // ERROR: missing arguments 6 val t = multiParamSum(_,_) // Ok, partial application: (Int, Int) => Int => Int 7 val y = multiParamSum(10,20)(_) // Ok, partial application: Int => Int 8 val u = multiParamSum(_,20)(_) // ERROR: missing parameter type 9 val i = multiParamSum(_:Int, 20)(_:Int) // Ok, partial application: (Int, Int) => Int R. Casadei Scala December 10, 2015 19 / 192
  20. 20. Basic Scala programming Basics Partial functions: trait PartialFunction[-A,+B] NOTE: partial functions ARE NOT partially applied functions A partial function is a unary function where the domain does not necessarily include all values of type A The function isDefinedAt allows to test dynamically if a value is in the domain of the function Note: a set of case clauses enclosed in braces is a partial function (i.e., a function which may not be defined for all inputs) 1 val evensMap: PartialFunction[Int, String] = { case x if x % 2 == 0 => x+" is even" } 2 3 // Builds a new collection by applying a partial function to all elements of this list on which the function is defined 4 val evenNumbers = sample collect evensMap 5 6 val oddsMap: PartialFunction[Int, String] = { case x if x % 2 == 1 => x+" is odd" } 7 8 // the method orElse allows chaining another partial function to handle input outside the declared domain 9 val numbers = sample map (evensMap orElse oddsMap) 10 11 evensMap.isDefinedAt(3) // => false 12 evensMap(3) // scala.MatchError: 3 R. Casadei Scala December 10, 2015 20 / 192
  21. 21. Basic Scala programming Basics Curried functions Currying is the conversion of a function of multiple parameters into a chain of functions that accept a single parameter Each function in the chain accept one argument and return another function until all args have been satisfied and a return value is made Sometimes, you want to use currying for a function param so that the type inferencer has more info 1 // Currying normal functions 2 def normalSum(a:Int, b:Int, c:Int) = a+b+c // normalSum: (a: Int, b: Int, c: Int)Int 3 val nsum = normalSum(_,_,_) // nsum: (Int, Int, Int) => Int = <function2> 4 val nsum2 = (a:Int, b:Int, c:Int) => a+b+c 5 val nsum3 : (Int,Int,Int)=>Int = _+_+_ 6 val nsumCurried = nsum.curried // nsumCurried: Int => (Int => (Int => Int)) = <function1> 7 8 // Curried function 9 def csum(a:Int) = (b:Int) => a+b 10 11 // Curried lambda 12 val csum2 = (a:Int) => (b:Int, c:Int) => a+b+c 13 14 // Example R. Casadei Scala December 10, 2015 21 / 192
  22. 22. Basic Scala programming Basics Parameters: default args, named args 1 // DEFAULT ARGUMENTS 2 def f(x: Int, mul: Int, dec: Int = 1) = x*mul - dec 3 4 // NAMED ARGUMENTS on call 5 f(dec=7, x=10, mul=1) // => 3 (you can specify them in any order) 6 f(10, dec=1, mul=7) // => 69 (you can mix named and unnamed args) 7 8 // VARIADIC FUNCTION (VARIABLE ARGUMENTS) 9 def g(args: Double*) = args.map (scala.math.sqrt _) 10 g(1,3,9) // Seq[Double] = ArrayBuffer(1.0, 1.7320508075688772, 3.0) 11 g(List[Double](1,2,3) :_* ) // Seq[Double] = List(1.0, 1.41421, 1.73205) 12 g( (1 to 3) map (_+0.0) :_*) // Seq[Double] = Vector(1.0, 1.4142, 1.7320) NOTE: Scala uses the static type of a variable to bind parameter names, however the defaults are determined by the runtime type 1 class A { def f(x: Int = 1, y: Int = 2) = x+y } 2 class B extends A { override def f(y: Int = 3, x: Int = 4) = x+y } 3 4 val a = new A; val b = new B; val c: A = new B 5 a.f(); // 3 (Defaults depends on runtime type) 6 b.f(); // 7 (Defaults depends on runtime type) 7 b.f(x=1); // 4 (Names depends on static type) 8 c.f(x=1); // 5 (Names depends on static type) R. Casadei Scala December 10, 2015 22 / 192
  23. 23. Basic Scala programming Basics Control abstractions You can model a seq of statements as a functions with no params or return value, i.e. of type () => Unit To avoid the syntax () => ... when creating a lambda of such type, you can use the call-by-name notation Unlike a call-by-value param, a call-by-value param is not evaluated when the function is called 1 def until(cond: => Boolean)(block: => Unit) { 2 if(!condition){ block; until(condition)(block) } 3 } 4 5 var x = 10 6 until(x == 0) { x-=1; println(x) } 7 // NOTE that x == 0 is not evaluated in the call of until In Scala, you don’t use return to return the function value as it is simply the value of the function body; rather, you can use return to return a value from an anonymous function to an encolosing named function (The control flow is achieved with a special exception thrown by the return expr) 1 def indexOf(str: String, ch: Char): Int = { 2 var i=0 3 until (i == str.length){ 4 if(str(i) == ch) return i 5 i += 1 6 } 7 -1 8 } R. Casadei Scala December 10, 2015 23 / 192
  24. 24. Basic Scala programming Basics Operators I Unary and binary operators are method calls Infix operators. a op b where op is a method with 2 params (one implicit, one explicit) 1 1 to 10 === 1.to(10) 2 1 -> 10 === 1.->(10) Unary operators. a op 1 1 toString === 1.toString() The four operators +, -. !, are allowed as prefix operators. They are converted as method calls with name unary_op 1 -a === a.unary_-() Assignment operators. a op= b means the same as a = a op b However, <=, >=, and != are not assignment ops, and an operator starting with = (e.g., ==, ===, =/=) is never an assignment op. If an object has a method operator=, then that method is called directly Associativity. In Scala, all operators are left-associative except for assignment operators and operators that end in a colon (:) 1 // In particular, :: for constructing lists is right associative 2 1 :: 2 :: Nil === 1 :: (2 :: Nil) 3 4 // A right-associative binary operator is a method of its second argument 5 1 :: 2 :: Nil === Nil.::(2).::(1) === List(1, 2) R. Casadei Scala December 10, 2015 24 / 192
  25. 25. Basic Scala programming Basics Operators II apply method: Scala allows you to use the function call syntax to values other than functions It is frequently used in companion objects to construct objects without calling new 1 obj(arg1, arg2, ... argN) === obj.apply(arg1, arg2, ..., argN) update: used to capture function-call-syntax on object followed by assignment 1 obj(arg1, ..., argN) = value === obj.update(arg1, ..., argN, value) 2 3 val scores = new scala.collection.mutable.HashMap[String, Int] 4 scores("Bob") = 100 // Calls scores.update("Bob", 100) 5 val bobScore = scores("Bob") // Calls scores.apply("Bob") R. Casadei Scala December 10, 2015 25 / 192
  26. 26. Basic Scala programming Basics Extractors I An extract is an object with an unapply method, which takes an object and extracts values from it You can think of unapply (from obj to values) as the opposite of apply (from values to obj) The return type can be one of Boolean: if it is just a test Option[T]: if it returns a single sub-value of type T Option[T1,...,TN]: if it returns several sub-values 1 class Fraction(val num: Int, val den: Int) { ... } 2 3 object Fraction { 4 def apply(n: Int, d: Int) = new Fraction(n, d) 5 6 def unapply(obj: Fraction): Option[(Int, Int)] = { 7 if(obj.den == 0) None else Some((obj.num, obj.den)) 8 } 9 } 10 11 var Fraction(a, b) = Fraction(3,4) * Fraction(2,5) // a,b initialized on result 12 // === Fraction.unapply( rhs ) 13 14 someFraction match { 15 case Fraction(n, d) => ... // === Fraction.unapply(someFraction) 16 case None => ... 17 } Every case class automatically has apply and unapply method To extract an arbitrary sequence of values, define an unapplySeq (it returns an Option[Seq[A]], where A is the type of the extracted field R. Casadei Scala December 10, 2015 26 / 192
  27. 27. Basic Scala programming Basics Extractors II 1 object Name { 2 def unapplySeq(input: String): Option[Seq[String]] = 3 if (input.trim == "") None else Some(input.trim.split("s+")) 4 } 5 // Now you can match for any num of vars 6 autor match { 7 case Name(first, last) => ... 8 case Name(first, middle, last) => ... 9 } R. Casadei Scala December 10, 2015 27 / 192
  28. 28. Basic Scala programming Basics Exceptions throw expressions have the special type Nothing. That is useful in if/else expressions: if one branch has type Nothing, the type of the if/else expr is the type of the other branch. 1 try { 2 throw new Exception() 3 } 4 catch { 5 case _: MalformedURLException => { } 6 case ex: IOException => ex.printStackTrace 7 case _ => () 8 } 9 finally { 10 ... 11 } Important: the return value of a try-catch-finally expression is the last expression of the try clause OR else clause I.e., the finally block is evaluated only for side effects R. Casadei Scala December 10, 2015 28 / 192
  29. 29. Basic Scala programming Basics Option I 1 sealed trait Option[+A] 2 case class Some[+A](get: A) extends Option[A] 3 case object None extends Option[Nothing] 4 5 trait Option[+A] { 6 def map[B](f: A => B): Option[B] 7 def flatMap[B](f: A => Option[B]): Option[B] 8 def getOrElse[B >: A](default: => B): B 9 def orElse[B >: A](ob: => Option[B]): Option[B] 10 def filter(f: A => Boolean): Option[A] 11 } Option[T] uses case classes Some(v) and None to express values that might or might not be present You can use getOrElse(defaultVal) or orElse(Some(someVal)) to provide a default in case you have None The most idiomatic way to use an Option instance is to treat it as a collection or monad and use map, flatMap, filter, or foreach In fact, you can see an Option[T] as a collection that contains zero or one elem of type T With flatMap we can construct a computation with multiple stages, any of which may fail, and the computation will abort as soon as the first failure is encountered, since None.flatMap(f) will immediately return None, without running f. A less-idiomatic way to use Option values is via pattern matching Methods that return an option get method of Map headOption and lastOption for lists and other iterables R. Casadei Scala December 10, 2015 29 / 192
  30. 30. Basic Scala programming Basics Option II 1 Option(1).toList // => List(1) 2 Option(1) foreach print // 1 3 List(1,2) ++ Some(3) // => List(1,2,3) 4 5 Some(5) map { case x if x%2==0 => "even"; case _ => "odd" } // => Some(odd) 6 Some(5) filter (_ % 2 == 0) // => Option[Int] = None 7 8 def isEven(x: Int) = x match { 9 case n if n%2==0 => Some(x); 10 case _ => None 11 } // isEven: (x: Int)Option[Int] 12 for { n <- 1 to 10; e <- isEven(n) } yield e // Vector(2, 4, 6, 8, 10) Create an object or return a default 1 val optFilename: Option[String] = retrieveInSomeWay(); 2 val dir = optFilename.map(name => new java.io.File(name)). 3 filter(_.isDirectory).getOrElse(new java.io.File(System.getProperty("java.io.tmpdir") )) Execute code if variable is initialized 1 val username: Option[String] = retrieveInSomeWay(); 2 for(uname <- username){ println("User: " + uname); } R. Casadei Scala December 10, 2015 30 / 192
  31. 31. Basic Scala programming Basics Either[T] 1 sealed trait Either[+E, +A] 2 case class Left[+E](value: E) extends Either[E, Nothing] 3 case class Right[+A](value: A) extends Either[Nothing, A] It epresents a value of one of two possible types (a disjoint union) A common use of Either is as an alternative to scala.Option for dealing with possible missing values. In this usage, Left works as None and Right works as scala.Some. Convention dictates that Left is used for failure and Right is used for success. A projection can be used to selectively operate on a value of type Either 1 val l: Either[String, Int] = Left("flower") 2 val r: Either[String, Int] = Right(12) 3 l.left.map(_.size): Either[Int, Int] // Left(6) 4 r.left.map(_.size): Either[Int, Int] // Right(12) 5 l.right.map(_.toDouble): Either[String, Double] // Left("flower") 6 r.right.map(_.toDouble): Either[String, Double] // Right(12.0) R. Casadei Scala December 10, 2015 31 / 192
  32. 32. Basic Scala programming Basics Annotations I Annotations are tags that you insert in the source code so that some tools (or the compiler/interpreter) can process them You can annotate classes, methods, fields, local vars, params, expressions, type params, and types. 1 // Annotation of a class 2 Entity class Credentials { ... } 3 // Annotation of a function/method 4 Test def testSomeFeature() { ... } 5 // Annotation of a var/val 6 BeanProperty Id var username = _ 7 // Annotation of a function arg 8 def doSomething( NotNull msg: String) { ... } 9 // Annotation of primary constructor 10 class A Inject() (/*primary constructor*/) {...} 11 // Annotation of an expression (Note the semicolon ’:’) 12 (expr: unchecked) match { ... } 13 // Annotation of type params 14 class A[ specialized T] 15 // Annotation of actual types are placed after the type 16 String scala.util.continuations.cps[Unit] With expressions and types, the annotation follows the annotated item. Some rules: annotations can have named arguments; if the arg name is value, its name can be omitted; if the annotation has no args, the parentheses can be omitted. Annotations can have default values. Arguments of Java annotations are restricted to a few types (numerics, strings, Class literals, enums, other annotations, arrays) An annotation must extend the annotation.Annotation class. Annotations extending this class directly are not preserved for the Scala type checker and are also not stored as Java annotations in classfiles R. Casadei Scala December 10, 2015 32 / 192
  33. 33. Basic Scala programming Basics Annotations II StaticAnnotations are available to the Scala type checker and are visible across compilation units. ClassfileAnnotations are stored as Java annotations in class files. Field definitions in Scala can give rise to multiple features in Java, all of which can potentially be annotated. E.g., class A(@NotNull @BeanProperty var name: String) gives raise to the constructor param, the private instance field, the getter, the setter, the bean getter and the bean setter. By default, constructor param annotations are only applied to the param itself, and field annotations are only applied to the field. You can use the meta-annotations @param,@field, @getter, @setter, @beanGetter, @beanSetter to attach the annotation elsewhere. 1 // Example of use of meta-annotation while defining an annotation 2 getter setter beanGetter beanSetter 3 class deprecated (message: String = "", since: String = "") extends annotation. StaticAnnotation 4 5 // Example of use of meta-annotation while annotating 6 Entity class Credentials { 7 (Id beanGetter) BeanProperty var id = 0 // Id applied to getId() method Annotations for interoperating with Java. @volatile, @transient, @strictfp, @native generate the Java-equivalent modifiers A volatile field can be updated in multiple threads (i.e., is subject to atomic reads/writes). A transient field is not serialized Methods marked with @native are implemented in C/C++ @strictfp restricts floating-point calculations to ensure portability (so, the prevent methods by using the 80-bit extended precision which Intel processors use by default) Scala uses @cloneable and @remote instead of the Cloneable and java.rmi.Remote marker interfaces R. Casadei Scala December 10, 2015 33 / 192
  34. 34. Basic Scala programming Basics Annotations III If you call a Scala method from Java code, its signature should include the checked exceptions that can be thrown (otherwise, the Java code wouldn’t be able to catch the exception). You can use @throws for the purpose. 1 class Book { 2 throws(classOf[IOException]) def read (fname: String) = {...} Annotations for optimizations When you rely on the compiler to remove the recursion on a method, you can mark it with @tailrec. If the compiler cannot apply the optimization, it will report an error. switch statements (in C++/Java) can often be compiled into a jump table which is more efficient than a list of if/else exprs. You can check if the compiler can provide the same for a match clause with @switch. 1 tailrec def myRecursiveMethod(..) = { ... } 2 3 (n: switch) match { ... } @varargs lets you call variable-arg Scala methods from Java 1 varargs def process(args: String*) = { ... } @elidable flags methods that can be removed in production code. For example, the assert function takes advantage of elidable annotation so that you can optionally remove assertions from programs. 1 elidable(500) def dump(...) { ... } 2 // The method won’t be generated if you compile with 3 // $ scalac -Xelide-below 800 myprog.scala R. Casadei Scala December 10, 2015 34 / 192
  35. 35. Basic Scala programming Basics Annotations IV Use @deprecated(message=“...”) to mark deprecated features and generate warnings on use. You can use @deprecatedName(’aSymbol) to specify a former name of a function parameter (i.e., you can still call myf(aSymbol=...) but you’ll get a warning). The @unchecked annotation suppresses a warning that a match is not exhaustive 1 (lst: unchecked) match { case head :: tail => ... } It’s inefficient to wrap/unwrap primitive type values, but in generic code this often happens. You can mark a type param as @specialized to get the compiler automatically generate overloaded versions of your generic method for the primitive types. 1 deff allDifferent[ specialized(Long, Double) T](x:T, y:T, z:T) = ... R. Casadei Scala December 10, 2015 35 / 192
  36. 36. Basic Scala programming Basics Functions vs. methods I Reference: http://stackoverflow.com/a/2530007/2250712 According to the Scala Language Specification A function type is roughly a type of form (T1,..,Tn)=>U which is a shorthand for the trait FunctionN Anonymous functions and method values have function types Function types can be used as part of value/variable/function declarations and definitions. In particular, a function type can be part of a method type A method type is a def declaration (everything about a def except its body) A method type is a non-value type, i.e., there is no value with a method type (i.e., objects can’t have method types) Variable declarations and definitions are vars. Value declarations and definitions are vals. vals and vars have both a type and a value. The type can be a function type (but not a method type), and in this case the value is an anonymous function or a method value. Note that, on the JVM, method values are implemented with Java methods A function declaration is a def declaration including type (the method type) and body (an expression or block) An anonymous function is an instance of a function type (i.e., instance of trait FunctionN) and a method value is the same thing: the distinction is that a method value is created from methods by either postfixing an underscore (m _) or by eta-expansion (which is like an automatic cast from method to function) If, instead of "function declaration", we say "method", we may say that a function is an object that includes one of the FunctionN traits (or PartialFunction) R. Casadei Scala December 10, 2015 36 / 192
  37. 37. Basic Scala programming Basics Functions vs. methods II Remember, FunctionN trait defines an abstract method apply(v1:T1,..,vN:TN):R Now, what is the similarity of a method and a function? It’s that they can be called in a similar way 1 f(...); 2 m(...); BUT the f call is actually desugared to f.apply(..) which is actually a method call. Another similarity is that methods can be converted to functions (but note that viceversa is not possible) 1 val f = m _ 2 // If "m" has type (List[Int])AnyRef 3 // Expands to: val f = new AnyRef with Function1[List[Int], AnyRef] { 4 // def apply(x$1: List[Int]) = this.m(x$1) 5 // } 6 // On Scala 2.8, it actually uses an AbstractFunction1 class to reduce class sizes Methods have one big advantage: they can receive type parameters. R. Casadei Scala December 10, 2015 37 / 192
  38. 38. Basic Scala programming Collections Outline 1 Basic Scala programming Basics Collections OOP in Scala Advanced features Programming techniques Practical usage Internal DSL implementation in Scala 2 Articles Scalable Component Abstractions R. Casadei Scala December 10, 2015 38 / 192
  39. 39. Basic Scala programming Collections Basics of collections in Scala I All collections extend the Iterable trait The 3 major categories of collections are sequences, sets, and maps Scala has mutable and immutable versions of most collections + adds an elem to an unordered coll (e.g., sets and maps), +: and :+ prepend or append to a sequence; ++ concatenates two collections, - and - remove elements R. Casadei Scala December 10, 2015 39 / 192
  40. 40. Basic Scala programming Collections Basics of collections in Scala II Scala’s collections split into 3 dichotomies 1 Immutable and mutable collections 2 Eager and delayed evaluation 3 Sequential and parallel evaluation There are two places to worry about collection types When creating generic methods that work against multiple collections ⇒ is all about selecting the lowest possible collection type that keeps the generic method performant When choosing a collection for a datatype ⇒ is all about instantiating the right collection type for the use case of the data E.g., Immutable List is ideal for recursive algorithms that split collections by head and tail R. Casadei Scala December 10, 2015 40 / 192
  41. 41. Basic Scala programming Collections Basics of collections in Scala III Mutable and immutable collections Scala collections systematically distinguish between mutable collections (scala.collection.mutable) and immutable collections (scala.collection.immutable On immutable collections, operations will return a new collection and leave the old collection unchanged Instead, mutable collections have some operations that change the collection in place Building new immutable collections is not inefficient because old e new ones share most of their structure A collection in package scala.collection can be either mutable or immutable. Typically, here we have root collections that define the same interface as immutable subclasses, whereas mutable subclasses add some side-effecting modification ops The scala package (which is automatically imported) define bindings (by default) for the immutable collections (i.e., scala.List alias scala.collection.immutable.List) A useful convention if you want to use both mutable and immutable versions of collections is to import just the package collection.mutable; then a word like Set without a prefix still refers to an an immutable collection, whereas mutable.Set refers to the mutable counterpart scala.collection.generic contains building blocks for implementing collections R. Casadei Scala December 10, 2015 41 / 192
  42. 42. Basic Scala programming Collections scala.collection These are all high-level abstract classes or traits, which generally have mutable as well as immutable implementations. R. Casadei Scala December 10, 2015 42 / 192
  43. 43. Basic Scala programming Collections Generic collections The collections hierarchy starts with the trait TraversableOnce, which represents a collection that can be traversed at least once and abstracts between Iterator, which is a stream of incoming items where advancing to the next item consumes the current item; key methods provided hasNext and next Traversable, which represents a collection that defines a mechanism to repeatedly traverse the entire collection Iterable is similar to Traversable but allows the repeated creation of an Iterator Then, the hierarcht branches out into sequences (Seq), maps (aka dictionaries, Map), sets (Set) Note: the aforementioned traits enforce sequential execution, i.e., they guarantee that operations are performed in a single-threaded manner. However, there are Gen* counterparts (GenTraversable, GenIterator, GenSeq, ...) that offer no guarantees on serial or parallel execution R. Casadei Scala December 10, 2015 43 / 192
  44. 44. Basic Scala programming Collections Traversable[+T] It implements the behavior common to all collections, in terms of the foreach method, which takes a function that operates on a single elem, and applies to every elem of the collection 1 // signature 2 def foreach[U](f: Elem => U): Unit The Traversable class has an efficient means of terminating foreach early when necessary (e.g., when using take(k) to limit a collection to the first k elems) foreach is easy to impl for any collection, but it’s suboptimal for many algorithms: it doesn’t support random access efficiently and requires one extra iteration when attempting to terminate traversal early 1 // Example 2 class FileLineTraversable(file: File) extends Traversable[String] { 3 override def foreach[U](f: String => U): Unit = { 4 val input = new BufferedReader(new FileReader(file)) 5 try { 6 var line = input.readLine 7 while(line != null){ 8 f(line) 9 line = input.readLine 10 } 11 } finally { input.close() } 12 } 13 } 14 15 // Usage 16 val x = ew FileLineTraversable(new java.io.File("test.txt")) 17 for { line <- x.take(2); word <- line.split("s+") } yield word R. Casadei Scala December 10, 2015 44 / 192
  45. 45. Basic Scala programming Collections Iterable[+T] Internal vs. external iterators Internal iterator (supported through Traversable): one where the collection or owner of the iterator is responsible for walking it through the collection External iterator (supported through Iterable): one where the client code can decide when and how to iterate Iterable is defined in terms of the iterator method, which returns an external iterator of type Iterator that can be used to walk through the items of the collection The Iterator supports two methods: hasNext and next; next throws an exception if there are no elems left We should use Iterable when explicit external iteration is required, but random access isn’t required One downside of external iterators is that collections such as FileLineTraversable are hard to impl One benefit is the ability to coiterate two collections 1 val a = Iterable(1,2,3); val b = Iterable (’a’,’b’,’c’,’d’) 2 val at = a.iterator; val bt = b.iterator 3 while(at.hasNext && bt.hasNext) print("("+at.next+"; "+bt.next+"),") 4 // (1; a),(2; b),(3; c); 5 6 // In one-line 7 a.iterator zip b.iterator map { case (a,b) => "("+a+"; "+b+")," } foreach print R. Casadei Scala December 10, 2015 45 / 192
  46. 46. Basic Scala programming Collections Seq[+T], LinearSeq[+T], IndexedSeq[+T] Seq represents collections that have sequential ordering and is def in terms of length, which returns collection size apply, which can be used to index into the collection by its ordering Seq offers no guarantee of performance of these operations. It should be used only to differentiate wrt Sets and Maps, i.e., when ordering is important and duplicates are allowed LinearSeq (Stack, ...) It is used to denote that a collection can be split into a head and tail component It is defined in terms of 3 “assumed to be efficient” methods: head, tail, and isEmpty IndexedSeq It implies that random access of collection elements is efficient (i.e., near constant) Indexing is done with the apply method (note that x.apply(2) can be abbreviated as x(2)) R. Casadei Scala December 10, 2015 46 / 192
  47. 47. Basic Scala programming Collections Set[T] Set denotes a collection where each element is unique, at least according to the == method Scala supports 3 types of sets 1 TreeSet is impl as a red black tree (RBT) of elements 2 HashSet is impl as a tree where elements are looked up using the hash value of a value 3 BitSet is impl as a sequence of Long values. It can store only integer values (it does so by setting the bit corresponding to that value in the underlying Long value) The basic rule of thumb is that if elements have an efficient hashing algorithm with low chance of collisions, then HashSet is preferred Sets extend from type (A) => Boolean, thus they can be used as a filtering function Use LinkedHashSet to retain the insertion order, or SortedSet to iterate in sorted order 1 (1 to 100) filter (1 to 10 map (_*10)).toSet // Vector(10,20,30,40,50,60,70,80,90,100) 2 3 val s = Set(1,2,3) + 0 // Set(1,2,3,0) 4 s + 0 + 0 + 0 // Set(1,2,3,0) 5 6 val s1 = Set(1,2,3,4) // Set(1, 2, 3, 4) 7 val s2 = s1 filter (_ % 2==0) // Set(2, 4) 8 val s3 = (s1 filter (_ % 2!=0)) + 5 // Set(1, 3, 5) 9 s2 | s3 // Set(5, 1, 2, 3, 4) 10 s1 & s2 // Set(2, 4) 11 s1 &~ s2 // Set(1, 3) R. Casadei Scala December 10, 2015 47 / 192
  48. 48. Basic Scala programming Collections scala.collection.Map[K,V] Map denotes a collection of key value pairs where only one value for a given key exists It provides an efficient lookup for values based on their keys Map has implementation types for HashMaps and TreeMaps (same considerations as for HashSet and TreeSet apply) A map can be used as a partial function from the key type to the value type withDefaultValue can be used to specify a default value to return when a key doesn’t exist 1 val m = Map("a" -> 1, "b" -> 2) // scala.collection.immutable.Map[String,Int] 2 m("a") // => 1 3 m("c") // NoSuchElementException 4 val m2 = Map("a" -> 1, 2.0 -> true) // Map[Any,AnyVal] (Etherogeneous keys and values) 5 m2(2) // => true (Note that an int is provided) 6 7 val m = Map(("a",1), ("z",2)) 8 (’a’ to ’z’) map (_.toString) map m // java.util.NoSuchElementException: key not found: b 9 (’a’ to ’z’) map (_.toString) filter m.keys.toSet map m // Vector(1, 2) 10 (’a’ to ’h’) map (_.toString) map m.withDefaultValue(0) // Vector(1, 0, 0, 0, 0, 0, 0, 0) 11 12 val mm = scala.collection.mutable.Map[String,Int]() 13 mm("a") = 77 // mm = Map(a -> 77) 14 mm += ("b" -> 88, "c" -> 99, "d" -> 55) // mm = Map(c -> 99, a -> 77, d -> 55, b -> 88) 15 mm -= ("a","c") // mm = Map(d -> 55, b -> 88) 16 val newmap = mm - "d" + ("e"->101, "f"->77) // newmap = Map(f -> 77, e -> 101, b -> 88) 17 18 val imm = Map[String,Int](newmap.keys zip newmap.values toSeq :_*) // Map(f->77, e->101, b->88) 19 for((k,v) <- imm if v%2!=0) yield k // List(f, e) 20 21 val smap = scala.collection.immutable.SortedMap(imm.iterator toSeq :_*) // Map(b->88, e->101, f->77) The -> method is from an implicit defined in scala.Predef which converts an expr such as A -> B to a tuple (A,B) R. Casadei Scala December 10, 2015 48 / 192
  49. 49. Basic Scala programming Collections Tuples Type (T1,T2,T3) === Tuple3[T1,T2,T3] 1 val c1 = (’a’) 2 val t1 = Tuple1(’a’) 3 val t2 = ("tag", 88, ’z’, ("mybool", true)) // (String, Int, Char, (String, Boolean)) = (tag,88,z,(mybool,true)) 4 t2._3 // z (Note: 1-indexed) 5 val (tag1, _, _, (tag2, _)) = t2 // tag1 = tag; tag2 = mybool (Assignment via pattern matching) 6 7 "New York".partition(_.isUpper) // => (String, String) = (NY,ew ork) R. Casadei Scala December 10, 2015 49 / 192
  50. 50. Basic Scala programming Collections Some notes on collection usage Immutable collections In general, use Vector When frequently performing head/tail decomposition, use List When you need a lazy list, use Stream Mutable collections Use Array when length is fixed, ArrayBuffer when length can vary ArrayBuffer is the mutable equivalent of Vector R. Casadei Scala December 10, 2015 50 / 192
  51. 51. Basic Scala programming Collections scala.collection.immutable R. Casadei Scala December 10, 2015 51 / 192
  52. 52. Basic Scala programming Collections Vector I Vector is a general-purpose, immutable data structure which provides random access and updates in effectively constant time, as well as very fast append and prepend Vector is currently the default impl of immutable indexed sequences It is backed by a little endian bit-mapped vector trie with a branching factor of 32. Locality is very good, but not contiguous, which is good for very large sequences R. Casadei Scala December 10, 2015 52 / 192
  53. 53. Basic Scala programming Collections Vector II Trie (aka prefix tree) A trie is a tree where every child in a given path down the tree shares some kind of common key. It’s the position of a node in the tree that defines the key with which it is associated. All the descendants of a node have a common prefix of the string associated with that node, and the root is associated with the empty string Normally, values are not associated with every node, only with leaves and some inner nodes that correspond to keys of interest R. Casadei Scala December 10, 2015 53 / 192
  54. 54. Basic Scala programming Collections scala.collection.immutable.List This class is optimal for last-in-first-out (LIFO), stack-like access patterns List extends from LinearSeq, as it supports O(1) head/tail decomposition and prepends List comes with two implementing case classes Nil and :: (cons cell, which holds a reference to a value and a reference to the rest of the list) that impl the abstract members isEmpty, head and tail Not efficient for random access Eagerly evaluated ⇒ the head and tail components of a list are known when the list is constructed 1 val lst1 = Nil.::("a").::(2.0).::(1) // lst1: List[Any] = List(1, 2.0, a) 2 // Note: In Scala, if an operator ends with ’:’, it is considered right-associative 3 // PREPEND: head :: tail 4 val lst2 = 1 :: 2.0 :: 3 :: Nil // lst2: List[AnyVal] = List(1, 2.0, 3) 5 lst2.head // => 1 6 lst2.tail // => List(2.0, 3) 7 8 0 +: List(1,2,3) // List(0, 1, 2, 3) [Prepend] 9 List(1,2,3) :+ 4 // List(1, 2, 3, 4) [Append] 10 List(1,2,3) ++ Set(4,5) // List(1, 2, 3, 4, 5) 11 List(0,1) ::: List(2,3,4) // List(0, 1, 2, 3, 4) R. Casadei Scala December 10, 2015 54 / 192
  55. 55. Basic Scala programming Collections Stream A stream is an immutable list in which the tail is computed lazily It can represent infinite sequences it remembers values that were computed during its lifetime, allowing efficient access to previous elements Like Lists, Streams are composed of cons cells (#::) and empty streams (Stream.empty); by contrast, a stream stores function objects that can be used to (lazily) compute its head and tail 1 val myStream = Stream from 1 // myStream: scala.collection.immutable.Stream[Int] = Stream(1, ?) 2 (’a’ to ’c’) zip myStream // => Vector((a,1), (b,2), (c,3)) 3 4 val s = 1 #:: { print("a"); 2 } #:: { print("b"); 3 } #:: Stream.empty // Stream(1,?) 5 s.tail // a Stream(2, ?) 6 s // Stream(1, 2, ?) 7 s.head // 1 8 9 val t = (1 to 100).toStream // t: scala.collection.immutable.Stream[Int] = Stream(1, ?) 10 t(4) // => 5 11 t // => scala.collection.immutable.Stream[Int] = Stream(1, 2, 3, 4, 5, ?) 12 // Note how elements that have been accessed are persisted 13 14 val fibs = { 15 def f(a:Int, b:Int): Stream[Int] = a #:: f(b, a+b) 16 f(0,1) 17 } // => Stream(0, ?) 18 fibs take(5) // => Stream(0, ?) 19 fibs // => Stream(0, ?) 20 fibs take(5) force // => List(0, 1, 1, 2, 3, 5) 21 fibs take(10) toList // => List(0, 1, 1, 2, 3, 5, 8, 13, 21, 34) 22 fibs // => Stream(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ?) 23 fibs drop(10) // => Stream(55, ?) 24 fibs // => Stream(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ?) R. Casadei Scala December 10, 2015 55 / 192
  56. 56. Basic Scala programming Collections scala.collection.mutable R. Casadei Scala December 10, 2015 56 / 192
  57. 57. Basic Scala programming Collections scala.Array[T] Arrays are mutable, indexed collections of values Predef provides additional functionality dynamically using scala.collection.mutable.ArrayLike Predef implicitly converts Array to scala.collection.mutable.ArrayOps which is a subclass of ArrayLike 1 val numbers = Array(1, 2, 3, 4) // Create 2 val first = numbers(0) // Read 3 numbers(3) = 100 // Update 4 numbers(4) = 5 // java.lang.ArrayIndexOutOfBoundsException: 4 (Array length is fixed) 5 6 // Traversal 7 for(i <- 0 until numbers.length) { println("nums["+i+"]="+numbers(i)) } 8 for(n <- numbers) println(n) // when no need for index 9 10 // Transforming arrays (doesn’t modify the original array, but yields a new one) 11 val doubled = numbers.map(_ * 2) // doubled: Array[Int] = Array(2, 4, 6, 8) 12 val result = for(n <- numbers if n%2==0) yield n+0.5 // result: Array[Double] = Array(2.5, 4.5) 13 14 // Common methods 15 val arr = (100 to 1000 by 250).toArray // Array(100, 350, 600, 850) 16 val z = for(n <- arr; r = new java.util.Random()) yield r.nextInt(n) // Array(69, 342, 437, 20) 17 z.max // 437 18 z.sum // 868 19 scala.util.Sorting.quickSort(z) // Unit (z has been SORTED IN PLACE) 20 z.mkString("<", ";", ">") // String = <20;69;342;437> 21 z.sorted // Array(20, 69, 342, 437) Returns a new (sorted) array 22 z.sortWith(_>_) // Array(437, 342, 69, 20) Returns a new (reversely sorted) array 23 24 // Multi-dimensional arrays 25 val matrix = Array.ofDim[Double](2,3) // Array(Array(0.0, 0.0, 0.0), Array(0.0, 0.0, 0.0)) 26 matrix(1,2) = 77 27 // Ragged arrays, with varying row lengths 28 val triangle = new Array[Array[Int]](10) R. Casadei Scala December 10, 2015 57 / 192
  58. 58. Basic Scala programming Collections scala.collection.mutable.ArrayBuffer[T] Buffers are used to create sequences of elements incrementally by appending, prepending, or inserting new elements ArrayBuffer is a Buffer impl that internally uses an array to represent the assembled sequence Append, update and random access take constant time (amortized time). Prepends and removes are linear in the buffer size. Amortized analysis examines how an algorithm will perform in practice or on average (in the long run you don’t care if an operation is slow once) 1 import scala.collection.mutable.ArrayBuffer 2 val ab = ArrayBuffer[Int]() 3 ab += 1 // ab= ArrayBuffer(1) 4 ab += (2,3,4) // ab= ArrayBuffer(1,2,3,4) Append multiple elems with += 5 ab ++= Set(100,77) // ab= ArrayBuffer(1,2,3,4,100,77) Append any collection with ++= 6 ab.trimStart(3) // ab= ArrayBuffer(4,100,77) Remove first 3 elems 7 ab.insert(2,0,88,0) // ab= ArrayBuffer(4,100,0,88,0,77) First arg is index, then elems 8 ab.remove(1,3) // ab= ArrayBuffer(4,0,77) Remove from 1 to 3 (inclusive) 9 10 val buff = Array(’a’,’b’).toBuffer 11 val arr = buff.toArray 12 13 val ab = ArrayBuffer[Int]() 14 ab += 3 += (4,5) // ab = ArrayBuffer(3, 4, 5) 15 6 +=: ab // ab = ArrayBuffer(6, 3, 4, 5) 16 (ab filter (_>4)) ++=: ab // ab = ArrayBuffer(5, 6, 6, 3, 4, 5) R. Casadei Scala December 10, 2015 58 / 192
  59. 59. Basic Scala programming Collections Mutation event publishing When ObservableMap, ObservableSet or ObservableBuffer are mixed into a collection, all mutations will get fired as events to observers The observers have the chance to prevent the mutation R. Casadei Scala December 10, 2015 59 / 192
  60. 60. Basic Scala programming Collections Lazy views Calling view on a collection yields a collection on which methods are applied lazily It yields a collection that is unevaluated (not even the first elem is evaluated) Unlike streams, these views do not cache any value 1 val squares = (0 to 10000000).view.map(math.pow(_,2)) 2 squares(3) // 9.0 3 squares(3) // 9.0 (Recomputed!) 4 squares.force // java.lang.OutOfMemoryError: Java heap space 5 // It forces computation of the entire collection R. Casadei Scala December 10, 2015 60 / 192
  61. 61. Basic Scala programming Collections Summary of operators for adding/removing elems from colls I Prepend/Append (Seq) coll :+ elem elem +: coll Add/Remove (Set, Map, ArrayBuffer) coll + elem coll + (e1, e2, ...) coll - elem coll - (e1, e2, ...) coll - coll2 Prepend/Append collection (Iterable) coll ++ coll2 cool2 ++: coll Prepend element/list to list (List) hd :: tailLst lst2 :: lst Set union/intersection/difference set | set2 set & set2 set &˜set2 On mutable collections coll += elem coll += (e1,e2,..) coll ++= coll2 coll -= elem R. Casadei Scala December 10, 2015 61 / 192
  62. 62. Basic Scala programming Collections Summary of operators for adding/removing elems from colls II coll -= (e1,e2,..) coll -= coll2 elem +=: coll coll2 ++=: coll R. Casadei Scala December 10, 2015 62 / 192
  63. 63. Basic Scala programming Collections Common methods I Important methods of the Iterable trait head, last, headOption, lastOption tail, init: return anything but the first or last element length, isEmpty map(f), foreach(f), flatMap(f), collect(pf): apply a function to all elems reduceLeft(op), reduceRight(op), foldLeft(init)(op), foldRight(init)(op): apply a binary op to all elems in a given order reduce(op), fold(init)(op), aggregate(init)(op, combineOp): apply a binary op to all elems in arbitrary order sum, product (provided the elem can be implicitly converted to Numeric trait) max, min (provided the elem can be implicitly converted to Ordered trait) count(pred), forall(pred), exists(pred) filter(pred), filterNot(pred), partition(pred) takeWhile(pred), dropWhile(pred), span(pred) take(n), drop(n), splitAt(n) takeRight(n), dropRight(n) slice(from, to): return the elems in the range zip(coll2), zipAll(coll2, fill, fill2), zipWithIndex: return pairs of elems from this coll and another grouped(n), sliding(n): return iterators of subcollections of length n; grouped yields elems with index 0 until n and then n until n*2 and so on; sliding yields elems with index 0 until n and then 1 until n+1 and so on R. Casadei Scala December 10, 2015 63 / 192
  64. 64. Basic Scala programming Collections Common methods II mkString(before, between, after), addString(stringBuilder, before, between, after) toIterable, toSeq, toIndexedSeq, toArray, toList, toStream, toSet, toMap copyToArray(arr), copyToArray(arr, start, length), copyToBuffer(buf) Important methods of the Seq trait contains(elem), containsSlice(seq), startsWith(seq), endsWith(seq) indexOf(elem), lastIndexOf(elem), indexOfSlide(seq), lastIndexOfSlice(seq) indexWhere(pred) prefixLength(pred), segmentLength(pred, n): return the length of the longest seq of elems fulfilling pred padTo(n, fill): return a copy of this seq, with fill appended until the length is n intersect(seq), diff(seq) reverse sorted, sortWith(less), sortBy(f): the seq sorted using the element ordering, the binary less function, or a function f that maps each elem to an ordered type permutations, combinations(n): return an iterator over the permutations or combinations R. Casadei Scala December 10, 2015 64 / 192
  65. 65. Basic Scala programming Collections Thread-safe collections The scala library provides 6 traits you can miz in with collections to synchronize their operations SynchronizedBuffer SynchronizedMap SynchronizedPriorityQueue SynchronizedQueue SynchronizedSet SynchronizedStack 1 import scala.collection.mutable._ 2 val scores = new HashMap[String,Int] with SynchronizedMap[String,Int] R. Casadei Scala December 10, 2015 65 / 192
  66. 66. Basic Scala programming Collections Parallel collections coll.par will produce a parallel implementation of the collection That implementation parallelizes the collection methods whenever possible E.g., it can compute the sum concurrently, or counting the elems that satisfy a predicate by analyzing subcollections in parallel and combining the results 1 Runtime.getRuntime().availableProcessors // 2 2 3 def time[R](block: => R): R = { 4 val t0 = System.nanoTime() 5 val result = block // call-by-name 6 val t1 = System.nanoTime() 7 println("Elapsed time: " + (t1 - t0) + "ns") 8 result 9 } 10 11 val coll = (1 to 1000000) // one million 12 13 time { coll.sum } // Elapsed time: 32199738ns res: Int = 1784293664 14 time { coll.par.sum } // Elapsed time: 18199941ns res: Int = 1784293664 R. Casadei Scala December 10, 2015 66 / 192
  67. 67. Basic Scala programming Collections Scala collections: performance characteristics R. Casadei Scala December 10, 2015 67 / 192
  68. 68. Basic Scala programming OOP in Scala Outline 1 Basic Scala programming Basics Collections OOP in Scala Advanced features Programming techniques Practical usage Internal DSL implementation in Scala 2 Articles Scalable Component Abstractions R. Casadei Scala December 10, 2015 68 / 192
  69. 69. Basic Scala programming OOP in Scala Classes: fields I Fields (declared with var or val) in classes automatically come with getters and setters If the field is private, the getter/setter are private If the field is a val, only a getter is generated If you don’t want any getter/setter, declare the field as private[this] (object-private) In Scala, a method can access the private fields of all objects of its class. private[this] can be used to qualify a field as object-private 1 class Counter { 2 private var value = 0 3 def isLess(other: Counter) = value < other.value 4 // other.value wouldn’t be allowed if value was private[this] Scala allows you to grant access rights to specific classes. private[ClassName] states that only methods of the given class can access the given field You can replace a field with a custom getter/setter without changing the clients of a class (uniform access principle) 1 class Person { 2 private var privateAge = 0 // Make private and rename 3 def age = privateAge // Note that the getter method is defined without () 4 def age_=(newVal: Int) { privateAge = newVal } // Note the syntax for defining the setter Note that there are no () in the def of the getter method. Therefore, you MUST call the method without parentheses: myCounter.age Annotate fields with @BeanProperty to gen the JavaBeans getXxx/setXxx methods R. Casadei Scala December 10, 2015 69 / 192
  70. 70. Basic Scala programming OOP in Scala Constructors Every class has a primary constructor that consists in the class body (i.e., it executes all the statements in the class definition) Params of the primary constructor turn into fields that are initialized with the construction params Construction params can also be regular method params (i.e., without val/var). How these are processed depends on their usage inside the class If a param without val/var is used in at least one method, it becomes a field (object-private) Otherwise, it is not saved as a field, it just can be accessed in the code of primary constructor Auxiliary constructors are optional. They are called this Each auxiliary constructor must start with a call to a previously defined auxiliary constructor or the primary constructor 1 class Person(val name: String){ 2 var nickName: String = _ 3 private var hobbies: List[String] = _ 4 5 def this(name: String, nickName: String, hobbies: List[String] = Nil){ 6 this(name) 7 this.nickName = nickName 8 this.hobbies = hobbies 9 } 10 } 11 12 val p = new Person("Roberto Casadei", "Robi") 13 p.name // => String = Roberto Casadei 14 p.name = "Marco Casadei" // error: reassignment to val 15 p.nickName = "obi" 16 p.hobbies // error: variable hobbies in class Person cannot be accessed in Person R. Casadei Scala December 10, 2015 70 / 192
  71. 71. Basic Scala programming OOP in Scala Nested classes In Scala, you can nest just about anything inside anything. You can def functions inside other functions, and classes inside other classes When you define a nested class B inside a class A, note that aObj1.B and aObj2.B are different classes. In other words, a nested class belongs to the object in which it is nested If you want a different behavior, you can move the inner class out of the outer class, or you can use type projection A#B (which means “a B of any A”) In a nested class, you can access the this reference of the enclosing class as EnclosingClass.this; moreover, you can establish an alias for that reference with the following syntax 1 class Network(val name: String) { outer => 2 class Member(val name: String){ 3 val contacts = new ArrayBuffer[Network#Member] // type projection 4 def description = name + " inside " + outer.name 5 } 6 7 private val members = new ArrayBuffer[Member] 8 def join(name: String) = { val m = new Member(name); members += m; m } 9 } R. Casadei Scala December 10, 2015 71 / 192
  72. 72. Basic Scala programming OOP in Scala Access modifiers As in Java, you have the public/private/protected access modifiers However, you can restrict access modifiers to entities (packages, classes, objects), via a syntax such as private[myPackage] In summary, in Scala you can choose between public: public access protected: inheritance access private: class-private access protected[package]: package-private with inheritance private[package]: package-private without inheritance private[this]: object-private access Moreover, classes can be declared as sealed, meaning that they can only by inherited in the same file in which they are defined. R. Casadei Scala December 10, 2015 72 / 192
  73. 73. Basic Scala programming OOP in Scala Objects The object keyword create a new singleton type, i.e., a type with only one value Use objects for singletons and utility methods Scala has NOT static methods in classes; rather, methods in companion objects are used for the purpose An object whose primary purpose is giving its members a namespace is sometimes called a module A class can have a companion object with the same name They must be located in the same source file The class and its companion object can access each other’s private features Objects can extend classes or traits The apply method of an object is usually used for constructing new instance of the companion class The constructor of an object is executed when the object is first used. An object can have essentially all the features of a class (e.g., for declaring fields). However, you cannot provide constructor params to objects R. Casadei Scala December 10, 2015 73 / 192
  74. 74. Basic Scala programming OOP in Scala Enumerations 1 object TrafficLightColor extends Enumeration { 2 val Red, Yellow, Green = Value 3 } 4 5 TrafficLightColor(1) // TrafficLightColor.Value = Yellow 6 TrafficLightColor.withName("Green") // TrafficLightColor.Value = Green 7 for(c <- TrafficLightColor.values) print(c.id + " ") // 0 1 2 Each call to Value method returns a new instance of an inner class ,also called Value You can pass IDs, names, or both to the Value method. If not specified, the ID is one more than the previously assigned one, starting with 0. The default name is the field name. Remember that the type of the enum is TrafficLightColor.Value and NOT TrafficLightColor (that’s the type of the object holding the values) 1 object TrafficLightColor extends Enumeration { 2 type TrafficLightColor = Value 3 val Red = Value(0, "Stop") 4 val Yellow = Value(10) // Name = "Yellow" 5 val Green = Value("Go") // ID = 11 6 } 7 8 import TrafficLightColor._ 9 val c: TrafficLightColor = TrafficLightColor.Red // now TrafficLightColor can be used as type R. Casadei Scala December 10, 2015 74 / 192
  75. 75. Basic Scala programming OOP in Scala Object equality equals and hashCode are defined in Any (parent of AnyRef and AnyVal) and can be overridden to implement custom equality/hashcode == and ## are final and are built upon equals and hashCode respectively ## calls hashCode except for null (throws NullPointerException) and for boxed numeric types returns a hash value consistent with value equality x==y is equivalent to {if(x eq null) y eq null else x.equals(y) The eq method in AnyRef checks if two references refer to the same object (object location equality). Custom equality When you implement a class, you should always consider overriding the equals method to provide a natural notion of equality for your situation 1 override def equals(other: Any) = { ... } // Note that it takes an arg of type Any Any impl of equals should be an equivalence relation (i.e., riflexive, symmetric, transitive) The equals and the hashCode should always be implemented in a consistent way, i.e., such that if x==y then x.##==y.## R. Casadei Scala December 10, 2015 75 / 192
  76. 76. Basic Scala programming OOP in Scala Polymorphic equality I In general, it’s best to avoid polymorphism with types requiring deep equality. Scala no longer supports subclassing case classes for this very reason But when we need to, we should implement equality comparisons correctly, keeping polymorphism in mind scala.Equals provides a template to make it easier to avoid mistakes: it provides a method canEqual that allows subclasses to opt out of their parent classes’ equality impl Problem 1 class A (val a: Int) { 2 override def equals(that: Any): Boolean = that match { 3 case o: A => if(this eq o) true else a == o.a 4 case _ => false 5 } 6 } 7 8 class B (a: Int, val b: Int) extends A(a) { 9 override def equals(that: Any): Boolean = that match { 10 case o: B => if(this eq o) true else a == o.a && b == o.b 11 case _ => false 12 } 13 } 14 15 val a = new A(1) 16 val b = new B(1,7) 17 a == b // Boolean = true 18 b == a // Boolean = false Solution: we need to modify the equality method in base class A to account for the fact that subclasses may whish to modify the meaning of equality. R. Casadei Scala December 10, 2015 76 / 192
  77. 77. Basic Scala programming OOP in Scala Polymorphic equality II 1 class A (val a: Int) extends Equals { 2 override def canEqual(other: Any) = other.isInstanceOf[A] 3 4 override def equals(that: Any): Boolean = that match { 5 case o: A => if(this eq o) true else a == o.a && o.canEqual(this) 6 case _ => false 7 } 8 } 9 10 class B (a: Int, val b: Int) extends A(a) { 11 override def canEqual(other: Any) = other.isInstanceOf[B] 12 13 override def equals(that: Any): Boolean = that match { 14 case o: B => if(this eq o) true else a == o.a && b == o.b && o.canEqual(this) 15 case _ => false 16 } 17 } 18 19 val a = new A(1) 20 val b = new B(1,7) 21 a == b // Boolean = false 22 b == a // Boolean = false R. Casadei Scala December 10, 2015 77 / 192
  78. 78. Basic Scala programming OOP in Scala Packages and imports I A package can contain classes, objects, and traits, but not the definitions of functions or variables Every package can have one package object, with the same name as the package and defined at the same level (i.e., in its parent package) 1 package a.b.c 2 3 package object people { 4 val defaultName = "John Q. Public" 5 def f { println("Hello") } 6 } 7 8 package people { ... } It’s common practice to define the package object for package a.b.c in file a/b/c/package.scala Packages are open-ended, you can contribute to a package at any time You can contribute to one or more packages in a single file 1 package com { ... } 2 package org.util { ... } Scope rules: everything in the parent package is in scope 1 package com { 2 object Utils { def f { ... } } 3 4 package innerPkg { 5 class AClass { Utils.f; /*...*/ } 6 } 7 } R. Casadei Scala December 10, 2015 78 / 192
  79. 79. Basic Scala programming OOP in Scala Packages and imports II Package paths are relative, not absolute 1 package com { 2 package example { 3 class Manager { 4 val subordinates = new collection.mutable.ArrayBuffer[Employee] 5 // it leverages on the fact that the scala package is always imported 6 } 7 } 8 } 9 // Suppose that someone introduces the following package, possibly in a different file 10 package com.collection { ... } 11 // Now the Manager class no longer compile, as it looks for a mutable member inside com.collection pkg 12 // Solutions: 1) use absolute package path, or 2) def Manager within chained package com.example You can also use absolute package paths, starting with the _root_ package Chained package clauses such as x.y.z leaves the intermediate packages x and x.y invisible package statements without braces at the top of the file extend to the entire file You can restrict the visibility of a class member to a package with private[pkg] 1 package a.b.c 2 3 class Person { 4 private[c] def desc = "This is visible in this package" 5 private[b] val xxx = "Extended visibility to an enclosing package" 6 } Imports Once you import a package, you can access its subpackages with shorter name import statements can be anywhere (not just at the top of the file) and extend until the end of the enclosing block In imports, you can hide/rename elements R. Casadei Scala December 10, 2015 79 / 192
  80. 80. Basic Scala programming OOP in Scala Packages and imports III 1 import java.awt._ // Import all members of a package 2 import java.awt.Color._ // You can import all members of a class or object 3 import java.awt.{Color, Font} // Import just a few members, using a selector like this 4 import java.util.{HashMap => JavaHashMap} // Rename 5 import java.util.{HashMap => _, _) // Hide member and import all others 6 import scala.collection.mutable._ // Now HashMap unambiguously refer to the mutable one Every Scala program implicitly starts with 1 import java.lang._ 2 import scala._ // This is allowed to override the preceding import 3 import Predef._ R. Casadei Scala December 10, 2015 80 / 192
  81. 81. Basic Scala programming OOP in Scala Inheritance The extends and final keywords are as in Java sealed means that the class/trait can be extended only in the same source file as its declaration – so you should use sealed if the num of possible subtypes is finite and known in advance You must use override when you override a method/field (unless the method was abstract). Note: you can override fields. Rules A def can only override another def A val can only override another val or a parameterless def A var can only override an abstract var Only the primary constructor can call the primary superclass constructor (because auxiliary constructors must call a preceding auxiliary constructor or the primary constructor) 1 class Employee(name: String, age:Int, val salary: Double) extends Person(name, age) Abstract classes, methods, fields 1 abstract class Person { 2 val id: Int // No initializer. It is an abstract field with an abstract getter method 3 var name: String // Another abstract field, with abstract getter/setter methods 4 def greet(s: String): String // No method body: this is an abstract method 5 } You can make an instance of an anonymous subclass if you include a block with definitions or overrides – process that is also called refinement 1 val alien = new Person("Fred") { 2 def flyAway { ... } 3 } 4 // Technically, this creates an object of a structural type 5 // The type is denoted as Person{def flyAway: Unit} R. Casadei Scala December 10, 2015 81 / 192
  82. 82. Basic Scala programming OOP in Scala Scala inheritance hierarchy The Any class is at the top. AnyVal (extends Any) is the root class for all value types. The classes that correspond to the primitive types in Java, as well as Unit, extend AnyVal All other classes are subclasses of AnyRef (which of course extends Any) and is a synonym for Java’s Object Any define methods such as isInstanceOf, asInstanceOf, eq, equals, hashCode AnyRef adds the monitor methods wait/notify/notifyAll from the Object class, and also provides a synchronized method with accepts a function parameter and is equivalent to a synchronized block in Java R. Casadei Scala December 10, 2015 82 / 192
  83. 83. Basic Scala programming OOP in Scala Type checks and casts If p is null, then p.isInstanceOf[T] returns false, and p.asInstanceOf[T] returns null (null is of type Null, which can’t be used in a type pattern or isInstanceOf test) 1 if (p.isInstanceOf[Employee]){ // p is of class Employee or of a subclass 2 val s = p.asInstanceOf[Employee]; 3 } 4 if (p.getClass == classOf[Employee]) { ... } // Check exact type 5 6 p match { // However, pattern matching is usually a better alternative 7 case s: Employee => ... 8 ... 9 } R. Casadei Scala December 10, 2015 83 / 192
  84. 84. Basic Scala programming OOP in Scala On visibility I Access modifiers are more sophisticated as in Java. You can restrict visibility to a package, class, or object using the syntax private[X] or protected[X] final: class can’t be inherited; field can’t be overridden sealed: the class can only be inherited in the same source file public: public access (it is the default – note it is different from Java’s package-private default) protected: inheritance access – means that any subclass can access the member (also from other objects of any subclass) private: class-private access – means that the member can be accessed only from the same class (also from other objects of the same class, no subclass) private[this]: object-private access R. Casadei Scala December 10, 2015 84 / 192
  85. 85. Basic Scala programming OOP in Scala On visibility II 1 // private (must fail when accessed in subclass) 2 class X(private val x: Int) 3 class Y extends X(0) { this.x } // ERROR 4 class Z extends X(0) { def otherx(other: X) = other.x } // ERROR 5 6 // private vs. private[this] 7 class X(private val x: Int) { def otherx(other: X) = other.x } // OK 8 class X(private[this] val x: Int) { def otherx(other: X) = other.x } // ERROR 9 10 // protected (can access from subclass) 11 class X(protected val x: Int); 12 class Y extends X(0) { this.x } // OK 13 14 // protected (on another object) 15 class X(protected val x: Int) 16 class Y extends X(0) { def otherx(other: X) = other.x } // ERROR (subtle) 17 class Z extends X(0) { def otherx(other: Z) = other.x } // OK 18 19 // protected[this] (must fail when accessed on another object) 20 class X(protected[this] val x: Int) 21 class Y extends X(0) { def otherx(other: Y) = other.x } // ERROR private[package]: package-private (without inheritance access) – means the member is accessible everywhere in the package protected[package]: package-private and inheritance access – means the member is accessible everywhere in the package and from any subclass (possibly in another package) R. Casadei Scala December 10, 2015 85 / 192
  86. 86. Basic Scala programming OOP in Scala On visibility III 1 package a { 2 class X(private[a] val x: Int) 3 class Y(protected[a] val y: Int) 4 5 package a.b { }; package object b { 6 def f = new X(7).x // OK (private[package] includes subpackages) 7 } 8 } 9 package object a { 10 def f = new X(7).x // OK 11 } 12 13 package c { 14 class Z extends a.Y(0) { this.y } // OK 15 } 16 package object c { 17 //def f = new a.X(7).x // ERROR 18 //def g = new a.Y(7).x // ERROR 19 } private and protected members can be accessed from the companion object 1 class Z(private val z:Int) 2 object Z { def zzz(z: Z) = z.z } 3 4 class Z(private[this] val z:Int); 5 object Z { def zzz(z: Z) = z.z } // ERROR (of course) You can set visibility up to an enclosing package (Note that you cannot limit visibility to an unrelated package) R. Casadei Scala December 10, 2015 86 / 192
  87. 87. Basic Scala programming OOP in Scala On visibility IV 1 package c { } 2 package a { 3 package b { 4 private class X 5 private[b] class X2 6 private[a] class Y 7 // private[c] class Z // ERROR: c is not an enclosing package 8 } 9 // class AX extends b.X // ERROR 10 // class AX2 extends b.X2 // ERROR 11 class AY extends b.Y // OK 12 } Similarly, you can set visibility up to an enclosing class 1 class X { 2 def y1(y: Y) = y.y1 3 //def y2(y: Y) = y.y2 // ERROR 4 //def y3(y: Y) = y.y3 // ERROR 5 6 7 class Y(private[X] val y1: Int, private val y2: Int, private[this] val y3: Int) 8 class Z(protected[X] val z1: Int, protected val z2: Int) 9 10 class SubY extends Y(1,2,3) { 11 this.y1 12 // this.y2 // ERROR 13 // this.y3 // ERROR 14 } 15 } 16 17 val x = new X 18 class SubZ extends x.Z(5,6) { this.z1 + this.z2 } // OK 19 R. Casadei Scala December 10, 2015 87 / 192
  88. 88. Basic Scala programming OOP in Scala On visibility V 20 class SubX extends X { 21 class SubZ extends Z(7,8) { this.z1 + this.z2 } // OK 22 } R. Casadei Scala December 10, 2015 88 / 192
  89. 89. Basic Scala programming OOP in Scala Traits I A trait is a special form of an abstract class which does not have any parameters for its constructor Traits can be used in all contexts where abstract classes can appear; however, only traits can be used for mixins Scala (like Java) does NOT provide multiple class inheritance (to avoid diamond inheritance problem and complexity) A trait can have both abstract and concrete methods/fields, and a class/object can implement multiple traits. With abstract fields/methods, a trait works as an interface Abstract fields/methods must be overridden in concrete subclasses Concrete fields/methods (which may depend on abstract ones) provide functionality that is “mixed into” the target object/class When you override an abstract method/field, you need not supply the override keyword, whereas you need it when overriding concrete members A class gets a field for each concrete fields in its traits: these fields are not inherited, but added to the class All Java interfaces can be used as Scala traits super.xxx() calls the next trait in the trait hierarchy, which depends on the order in which traits are added When overriding and calling (via super) at the same time an abstract field/method, you must decorate the new abstract implementation with abstract override When implementing multiple traits, you use extends before the first trait and with before the other traits. You can add a trait to an individual object when you construct it R. Casadei Scala December 10, 2015 89 / 192
  90. 90. Basic Scala programming OOP in Scala Traits II 1 trait Logger { 2 def log(msg: String) // Abstract method 3 4 def info(msg: String) { log("INFO: " + msg) } // Concrete method 5 def warn(msg: String) { log("WARN: " + msg) } // Concrete method 6 } 7 8 trait ShortLogger extends Logger { 9 val maxLength:Int // Abstract field 10 val ellipsis = "..." // Concrete field 11 12 abstract override def log(msg: String){ // NOTE: abstract override 13 super.log(msg.take(maxLength)+ellipsis) 14 } 15 } 16 17 trait ConsoleLogger extends Logger { 18 override def log(msg: String) { println(msg) } 19 } 20 21 class Employee extends Logger with Serializable with Cloneable { 22 def log(msg: String) { } 23 } 24 25 val p = new { // Early definition 26 val maxLength=5; // ’override’ not needed as field is abstract 27 override val ellipsis=".." // ’override’ needed as field is not abstract 28 } with Person with ConsoleLogger with ShortLogger 29 p.log("Hello world") // Hello.. 30 // NOTE: due to mixin-order, ShortLogger’s super refers to ConsoleLogger R. Casadei Scala December 10, 2015 90 / 192
  91. 91. Basic Scala programming OOP in Scala Traits III A trait can also extend a class. That class becomes a superclass of any class mixing in the trait. The class implementing extending the trait can extends another class if that class is a subclass of the trait’s superclass 1 trait LoggedException extends Exception with Logger { 2 def log() { log(getMessage()) } // Note: getMessage() is inherited from Exception 3 } 4 5 class MyException extends IOException with LoggedException { 6 override def getMessage() = "arggh!" 7 } R. Casadei Scala December 10, 2015 91 / 192
  92. 92. Basic Scala programming OOP in Scala Construction order I Construction order 1 Superclass constructor 2 Trait constructors left-to-right (with parents constructed first) 3 Class constructor Note: if multiple traits share a common parent, and that parent has already been constructed, it is not constructed again Example 1 class A { print("A") } 2 trait H { print("H") } 3 trait S extends H { print("S") } 4 trait R { print("R ") } 5 trait T extends R with H { print("T") } 6 class B extends A with T with S { print("B") } 7 8 new B // A R H T S B The constructor ordering is the reverse of the linearization of the class, which is the process of specifying the linear ordering of superclasses of that class C extends C1 with C2 · · · with CN ⇒ lin(C) = C >> lin(CN ) >> ... >> lin(C1) Where >> means "concatenate and remove duplicates, with the right winning out" In the previous example we have R. Casadei Scala December 10, 2015 92 / 192
  93. 93. Basic Scala programming OOP in Scala Construction order II 1 2 lin(B) = B >> lin(S) >> lin(T) >> lin(A) 3 = B >> (S >> H) >> (T >> H >> R) >> A 4 = B >> S >> H >> T >> R The linearization gives the order in which super is resolved in a trait. This means that an implementer of a trait doesn’t necessarily know which type super will be until linearization occurs In the example above, calling super in S invokes the T method I.e., multiple traits can invoke each other starting with the last one in the trait list (i.e., things linearize right to left wrt to order of appearing in class declaration) I.e., the first traits in the trait list are at higher levels of the hierarchy (and needs to be constructed before, as they may be used by traits more on the right) Similarly, if multiple traits override the same member, the override that wins depends on the mixin-order (i.e., the last-constructed trait wins) You can control which trait’s method is invoked by specifying super[OneParentTrait], where the specified type must be an immediate supertype. R. Casadei Scala December 10, 2015 93 / 192
  94. 94. Basic Scala programming OOP in Scala Initializing fields and early definitions I Traits cannot have constructor params: every trait has a single parameterless constructor. There is a pitfall related to constructor order and trait fields: 1 trait FileLogger extends Logger { 2 val filename: String 3 val out = new PrintStream(filename) 4 def log(msg: String) { out.println(msg); out.flush() } 5 } 6 val acct = new SavingsAccount with FileLogger { 7 val filename = "myapp.log" // this doesn’t work! 8 } It doesn’t work because the FileLogger constructor runs before the subclass constructor Solution A: early definitions 1 val acct = new { 2 val filename = "myapp.log" 3 } with SavingsAccount with FileLogger // Note that the class name is provided after ’with’ 4 5 // It works also for classes 6 class SavingsAccount extends { 7 val filename = "savings.log" 8 } with Account with FileLogger { ... } Solution B: lazy fields R. Casadei Scala December 10, 2015 94 / 192
  95. 95. Basic Scala programming OOP in Scala Initializing fields and early definitions II 1 trait FileLogger extends Logger { 2 val filename: String 3 lazy val out = new PrintStream(filename) 4 def log(msg: String) { out.println(msg) } 5 } R. Casadei Scala December 10, 2015 95 / 192
  96. 96. Basic Scala programming OOP in Scala Self types When a trait starts out with this: AType => then it can only be mixed into a subclass of the given type. In the trait methods, we can call any methods of the self type Self types can also handle structural types (types that merely specify the methods that a class must have, without naming the class) 1 trait LoggedException extends Logger { 2 this: Exception => 3 def log(){ log(getMessage()) } 4 } 5 6 trait LoggedException extends Logger { 7 this: { def getMessage():String } => 8 def log() { log(getMessage()) } 9 } R. Casadei Scala December 10, 2015 96 / 192
  97. 97. Basic Scala programming OOP in Scala What happens under the hood with traits Scala needs to translate traits into classes and interfaces of the JVM A trait that has only abstract methods is simply turned into a Java interface If a trait has concrete methods, a companion class is created whose static methods hold the code of the trait’s method Fields in traits yield abstract getters/setters in the interface When a class implements a trait, the fields are added to that class When a trait extends a superclass, the companion class does not inherit that superclass; instead, any class implementing the trait extends the superclass 1 trait Logger { 2 def log(msg: String) 3 } 4 5 trait ShortLogger extends Logger { 6 val maxLength = 15 7 8 def log(msg: String){ println(msg.take(maxLength) } 9 } 10 11 // === The following interfaces/classes are generated === 12 // ====================================================== 13 14 public interface Logger { void log(String msg); } 15 16 public interface ShortLogger extends Logger { 17 void log(String msg); 18 19 public abstract int maxLength(); 20 public abstract void weird_prefix$maxLength_$eq(int); // used for field initialization 21 } 22 23 publiic class ShortLogger$class { 24 public static void log(ShortLogger self, String msg){ ... } 25 26 public void $init$(ShortLogger self){ 27 self.weird_prefix$maxLength_$eq(15) 28 } 29 } R. Casadei Scala December 10, 2015 97 / 192
  98. 98. Basic Scala programming OOP in Scala Value classes Value classes are subclasses of AnyVal. They provide a way to improve performance on user-defined types by avoiding object allocation at runtime, and by replacing virtual method invocations with static method invocations 1 class Wrapper(val underlying: Int) extends AnyVal { 2 def foo: Wrapper = new Wrapper(underlying * 19) 3 } In this example, the type at compile time is Wrapper, but at runtime, the representation is an Int A value class can define defs, but no vals, vars, or nested traits, classes or objects A value class can only extend universal traits and cannot be extended itself. A universal trait is a trait that extends Any, only has defs as members, and does no initialization. Universal traits allow basic inheritance of methods for value classes, but they incur the overhead of allocation Use cases Value classes are often combined with implicit classes for allocation-free extension methods 1 implicit class RichInt(val self: Int) extends AnyVal { 2 def toHexString: String = java.lang.Integer.toHexString(self) 3 } Another use case for value classes is to get the type safety of a data type without the runtime allocation overhead 1 class Meter(val value: Double) extends AnyVal { 2 def +(m: Meter): Meter = new Meter(value + m.value) 3 } 4 val x = new Meter(3.4); val y = new Meter(4.3) 5 val z = x + y R. Casadei Scala December 10, 2015 98 / 192
  99. 99. Basic Scala programming OOP in Scala OOD - Rules of thumbs I Rule 1) Avoid abstract val in traits – Using abstract values in traits requires special care with object initialization. Early initializer blocks can solve this lazy vals can be a simpler solution Even better is to avoid these dependencies by using abstract classes and constructor parameters 1 trait A { 2 val msg: String // Abstract field 3 override val toString = "Msg: " + msg 4 } // Note: a ’val’ can override a parameterless ’def’ 5 6 val x = new A { override val msg = "Hello" } // x: java.lang.Object with A = Msg: null 7 // This is because trait A is initialized first during construction 8 9 // Can be solved via early initializers 10 // Flavor 1) 11 class B extends { val msg = "Hello" } with A {} 12 val y = new B // y: B = Msg: Hello 13 // Flavor 2) 14 val z = new { val msg = "Hello" } with A // z: java.lang.Object with A = Msg: Hello R. Casadei Scala December 10, 2015 99 / 192
  100. 100. Basic Scala programming OOP in Scala OOD - Rules of thumbs II Rule 2) Provide empty implementations for abstract methods on traits It’s common to use traits to define mix-in behaviors, possibly with base traits providing default behavior In the chain-of-command pattern, we want to define a base set of functionality and defer the rest to a parent class However, Scala traits have no defined superclass until they have been mixed in and initialized. For a trait, the type of super is known during class linearization. Thus, our choices are 1 Define a self-type (but this approach limits how your trait could be mixedi in) 1 trait B { def b = println("b") } 2 trait C extends B { override def b = println("c") } 3 4 trait A { self: B => def a = b } // a() doesn’t delegate to parent but to self type 5 6 (new A with C{}).a // Prints: c 2 Make the abstract method have a default "do-nothing" implementation that would get called 1 trait B { def b = {} } // Default impl 2 trait A extends B { def a = b } 3 trait C extends B { override def b = println("c") } 4 5 (new A {}).a // Calls default impl 6 (new A with C {}).a // Prints: c R. Casadei Scala December 10, 2015 100 / 192
  101. 101. Basic Scala programming OOP in Scala OOD - Rules of thumbs III 1 // B defines the abstract behavior; C provides the default impl; 2 // A calls/use the behavior; D provides an additional impl of the behavior 3 4 trait B { def b: Unit } 5 trait C extends B { override def b = {} } 6 trait D extends B { override def b = println("d") } 7 8 trait A extends C { def a = super.b } 9 10 (new C with D with A{}).a // Prints: d (!!!!!!!!!!!!) 11 (new D with A{}).a // Prints nothing (calls default impl. of C) When creating a hierarchy of mixable behaviors via trait, you need to ensure that You have a ixin point that traits can assume as a parent Your mixable traits delegate to their parent in meaningful ways You provide default implementations for chain-of-command style methods at your mixin points R. Casadei Scala December 10, 2015 101 / 192
  102. 102. Basic Scala programming OOP in Scala OOD - Rules of thumbs IV Rule 3) Promote abstract interface into its own trait – It’s possible to mix implementation and interface with traits, but it is still a good idea to provide a pure abstract interface. When creating two different "parts" of a software program, it’s helpful to create a completely abstract interface between them that they can use to talk to each other. It may seems this rule collides with rule "provide empty impl for abstract methods" – Actually, these 2 rules solve different problems. Use this rule when trying to create separation between modules. Provide impl for abstract methods when creating a lib of traits you intend users to extend via mixins. Pure abstract traits also help explicitly identify a mininum interface. Such a "thin" interface should be something we can reasonably expect someone to implement completely. R. Casadei Scala December 10, 2015 102 / 192
  103. 103. Basic Scala programming OOP in Scala Composition and inheritance in Scala I Some terminology Inheritance-composition: composition of behavior via inheritance Member-composition: composition of behavior via members of an object Issues with inheritance wrt Java interfaces, abstract classes, and traits Need to reimplement behavior in subclasses – applies only to Java interfaces Can only compose with parent behavior – applies only to Java abstract classes Breaks encapsulation – applies to all of them Inheritance breaks encapsulation because functionality splits between the interface/class/trait and its base interface/class/trait Need to call a constructor to compose – applies to all of them Compositional methods Member composition 1 trait Logger { def log(s: String) = println(s) } 2 trait DataAccess { 3 // NOTE: we can compose via constructor injection (but we need to use abstract class, no trait) 4 val logger = new Logger {} // Member-composition 5 def query(q: String) = { logger.log(".."); ... } 6 } 7 // ISSUE: DataAccess contains all logging behavior R. Casadei Scala December 10, 2015 103 / 192
  104. 104. Basic Scala programming OOP in Scala Composition and inheritance in Scala II 1 trait Logger { def log(s: String) = println(s) } 2 trait DataAccess { def query(q: String) = ... } 3 // Now DataAccess is unaware of any logging 4 trait LoggedDataAccess { 5 val logger = new Logger {} 6 val dao = new DataAccess {} 7 def query(q: String) = { logger.log(".."); dao.query(q) } 8 } 9 // ISSUE: LoggedDataAccess doesn’t impl the DataAccess interface Inheritance composition 1 // Inheritance composition 2 trait LoggedDataAccess extends DataAccess with Logger { 3 def query(q: String) = { log(".."); super.query(q) } 4 } 5 6 // Mixed inheritance-composition approach 7 trait LoggedDataAccess extends DataAccess { 8 val logger = new Logger {} 9 def query(q: String) = { logger.log(".."); super.query(q) } 10 } Abstract member composition (member composition by inheritance) R. Casadei Scala December 10, 2015 104 / 192
  105. 105. Basic Scala programming OOP in Scala Composition and inheritance in Scala III 1 def ‘...‘: Unit = {} // To make this example compilable :) 2 3 trait Logger { def log(s: String) = println(s) } 4 trait RemoteLogger extends Logger { override def log(s: String) = ‘...‘ } 5 trait NullLogger extends Logger { override def log(s: String) = {} } 6 7 trait HasLogger { val logger: Logger = new Logger{} } 8 trait HasRemoteLogger extends HasLogger { override val logger = new RemoteLogger{} } 9 trait HasNullLogger extends HasLogger { override val logger = new NullLogger{} } 10 11 trait DataAccess extends HasLogger { 12 def query(q: String) = { logger.log("Performing query"); ‘...‘ } 13 } 14 val dataAccess = new DataAccess {} 15 dataAccess.query("xxx") // Prints: Performing query 16 val dataAccessMock = new DataAccess with HasNullLogger { } 17 dataAccessMock.query("xxx") // Prints nothing 18 // Note, we could achieve the same via refinement 19 val dataAccessMock2 = new DataAccess { override val logger = new NullLogger{} } Composition using constructor with default arguments 1 class DataAccess(val logger: Logger = new Logger {}) { ... } R. Casadei Scala December 10, 2015 105 / 192
  106. 106. Basic Scala programming Advanced features Outline 1 Basic Scala programming Basics Collections OOP in Scala Advanced features Programming techniques Practical usage Internal DSL implementation in Scala 2 Articles Scalable Component Abstractions R. Casadei Scala December 10, 2015 106 / 192
  107. 107. Basic Scala programming Advanced features On Scala’s Type System I In general, a type system enables lots of rich optimizations and constraints to be used during compilation, which prevents programming errors and helps runtime speed The more you know about Scala’s type system and the more information you can give the compiler, the “type walls” become less restrictive while still providing the same protection. A type can be thought as a set of information the compiler knows about entities In Scala, you can explicitly provide type information or let the compiler infer it through code inspection. In Scala, types can be defined in two ways: 1 Defining a class / trait / object 2 Directly defining a type using the type keyword Defining a class/trait/object automatically creates an associate type. Now the question is: how can we refer to that type? For a class/trait, we can refer to its type simply through the class/trait’s name For objects, this is slightly different (myobj.type) due to potential of classes/traits having the same name as an object R. Casadei Scala December 10, 2015 107 / 192
  108. 108. Basic Scala programming Advanced features On Scala’s Type System II 1 class C; trait T; object O 2 3 def c(c: C) = c 4 def t(t: T) = t 5 def o(o: O.type) = o // Note how we refer to an object’s type Why would you like to define a method parameter of an object’s type? For example, it may be useful when developing DSLs 1 object now 2 object simulate { 3 def once(behavior: => Unit) = new { 4 def right(when: now.type): Unit = when /*...*/ 5 } 6 } 7 simulate once { println("ciao") } right now In Scala, types are referenced relative to a binding or path A binding is the name used to refer to an entity. This name could be imported from another scope. A path is a location where the compiler can find types. A path is NOT a type. A path could be one of the following: Empty path – when a type name is used directly, there’s an implicit empty path preceding it C.this where C is a class Using this within a class C is a shorthand for C.this This path is useful for referring to identifiers defined on outer classes p.x where p is a path and x is a stable member of p R. Casadei Scala December 10, 2015 108 / 192
  109. 109. Basic Scala programming Advanced features On Scala’s Type System III Stable members are packages, objects, or value definitions introduced on nonvolatile types A volatile type is a type where the compiler can’t be certain its members won’t change (e.g., an abstract type definition on an abstract class – the type definition could change depending on the subclass) A stable identifier is a path that ends with an identifier C.super.x or C.super[P].x where x is a stable member of the superclass of the class referred by C Using super directly within a class C is a shorthand for C.super Use this path to disambiguate between identifiers defined on a class and a parent class In Scala, you can refer to types using two mechanisms The dot operator . refers to a type found on a specific object instance (path-dependent type) Type foo.Bar matches Bar instances generated from the same instance referred by foo NOTE: the dot operator needs an object on the LHS The hash operator # refers to a nested type without requiring a path of object instances (type projection) Type Foo#Bar matches Bar instances generate from any instance of Foo NOTE: the hash operator needs a type on the LHS R. Casadei Scala December 10, 2015 109 / 192

×