CSC447
Concepts of Programming Languages
Scala Introduction
Instructor: James Riely
Scala
-
Functional and object-oriented PL
-
Compiles to JVM
-
Interop: Scala calls Java; Java calls Scala
-
Examples
Everything Is An Object
-
5:Int
is an object with methods
5.toDouble
-
Methods can have symbolic names
(See
scala.Int
)
5.+ (6)
-
scala.runtime.RichInt
adds more methods
5.max (6)
-
e1.f(e2)
can be written as e1 f e2
5 + 6
5 max 6
Immutable Variables
Java
final int x = 10;
x = 11;
Final.java:4: error: cannot assign a value to final variable x
C
const int x = 10;
x = 11;
final.c:6:3: error: assignment of read-only variable ‘x’
Scala
val x = 10
x = 11
final.scala:3: error: reassignment to val
Expression Oriented
C comma expressions
(e_1, e_2, ..., e_n)
Scala compound expressions
{e_1; e_2; ...; e_n}
Semicolons optional (inferred, whitespace sensitive)
{
e_1
e_2
...
e_n
}
Methods
Conditional expressions for factorial
def fact (n:Int) : Int = if n <= 1 then 1 else n * fact (n - 1)
Compound expressions for side-effects
def fact (n:Int) : Int =
println ("called with n = %d".format (n))
if n <= 1 then
println ("no recursive call")
1
else
println ("making recursive call")
n * fact (n - 1)
Syntax like C statements, but uses expressions
Methods versus fields
-
def
can be used as non-parameterized methods
-
val
and def
differ in when initializer is executed.
-
val
is strict (execute when declared); def
is non-strict (execute when accessed)
scala> class C:
val x = 1
def y = 1
scala> :javap -p -c -filter C
public class C {
private final int x;
public int x();
1: getfield #18 // Field x:I
public int y();
0: iconst_1
public C();
10: putfield #18 // Field x:I
}
Mutable fields
scala> class C:
val x = 1
var z = 1
scala> :javap -p -c -filter C
public class C {
private final int x;
private int z;
public int x();
1: getfield #19 // Field x:I
public int z();
1: getfield #23 // Field z:I
public void z_$eq(int);
2: putfield #23 // Field z:I
public C();
6: putfield #19 // Field x:I
11: putfield #23 // Field z:I
}
Scala Type Checking
-
Scala performs static type checking
def f () = 5 - "hello"
-
REPL prints types of expressions
-
Java to Scala type hierarchy
-
Java primitive types to Scala value types
-
Java reference types to Scala reference types
-
java.lang.Object
to scala.AnyRef
Structured Data
-
Tuples
-
Fixed number of heterogeneous items
-
Lists
-
Variable number of homogeneous items
-
Immutable and mutable variants
-
Pattern matching
Mutability: Fields versus Data
-
Field mutability is different from data mutability
-
Java mutable linked list with final variable
final List<Integer> xs = new List<> ();
xs.add (4); xs.add (5); xs.add (6);
xs = new List<> ();
-
Scala immutable linked list with
var
variable
var xs = List (4, 5, 6)
xs = 0 :: xs
xs (1) = 7
Immutable Tuples
Scala
val p : (Int, String) = (5, "hello")
val x : Int = p(0)
Java
| public class Pair<X,Y> { |
| final X x; |
| final Y y; |
| public Pair (X x, Y y) { this.x = x; this.y = y; } |
| |
| static void f () { |
| Pair<Integer, String> p = new Pair<Integer, String> (5, "hello"); |
| Pair<Integer, String> q = new Pair<> (5, "hello"); |
| int x = p.x; |
| } |
| } |
Pattern Matching Behavior
The behavior of pattern matching...
def a(p:(Int,Int)) = p match
case (x,y) => x+y
...branches and binds
pattern variables
| def b(p:(Int,Int)) = |
| val x = p(0) |
| val y = p(1) |
| x + y |
Immutable Linked Lists
-
Scala's
::
is an infix cons
operator
Lisp
(define xs (cons 11 (cons 21 (cons 31 (cons 41 ())))))
Scala
val xs = 11 :: (21 :: (31 :: (41 :: Nil)))
val xs = 11 :: 21 :: 31 :: 41 :: Nil
Immutable Linked Lists
-
Projection called head and tail in many PLs
Immutable Linked Lists
-
Constructors for linked lists
Pattern Matching Behavior
The behavior of pattern matching...
def f(xs: List[Int]) = xs match
case Nil => "List is empty"
case x::xt => "List is non-empty, head is %d".format (x)
...branches and binds
pattern variables (simplified)
| def g(xs: List[Int]) = |
| if xs == Nil then "List is empty" |
| else |
| val x : Int = xs.head |
| val xt : List[Int] = xs.tail |
| "List is non-empty, head is %d".format (x) |
Pattern Matching Behavior
Actual code requires casting:
| def g(xs: List[Int]) = |
| if xs == Nil then "List is empty" |
| else if xs.isInstanceOf[::[Int]] then |
| val xc = xs.asInstanceOf[::[Int]] |
| val x : Int = xc.head |
| val xt : List[Int] = xc.tail |
| "List is non-empty, head is %d".format (x) |
| else throw MatchError(xs) |
Nesting patterns
-
Patterns can include other patterns
def f (xs: List[(Int,String)]) = xs match
case Nil => "List is empty"
case _::Nil => "List has one element"
case _::(x,_)::_ => s"The second int is ${x}"
val zs = List ((11,"dog"), (21,"cat"), (31,"pig"))
f(zs)
-
Found in ML, Haskell, Rust, Swift, and coming to Java
-
_
means don't care
Pattern Matching
-
Pattern matching often improves readability
def f (xs: List[(Int,String)]) =
if xs == Nil then "List is empty"
else if xs.tail == Nil then "List has one element"
else s"The second int is ${xs.tail.head(0)}"
val zs = List ((11,"dog"), (21,"cat"), (31,"pig"))
f(zs)
Simple List Operations
-
Implement
isEmpty
, head
, tail
by pattern matching
def isEmpty (xs:List[Int]) : Boolean = xs match
case Nil => true
case x::xt => false
def head (xs:List[Int]) : Int = xs match
case Nil => throw NoSuchElementException ()
case x::xt => x
def tail (xs:List[Int]) : List[Int] = xs match
case Nil => throw NoSuchElementException ()
case x::xt => xt
Builtin Methods
-
Builtin
head
method from List
class
List (1, 2, 3).head
-
head
method defined on previous slide
head (List (1, 2, 3))
Recursive Methods
-
Imperative programming typically has
-
mutable programming
-
iteration using while loops
-
Functional programming typically has
-
immutable programming
-
iteration using recursion
-
Efficient method calls / recursion
Recursive Methods: Lists
Length of a linked list recursively
def length (xs:List[Int]) : Int = xs match
case Nil => 0
case x::xt => 1 + length (xt)
With parametric polymorphism
def length [X] (xs:List[X]) : Int = xs match
case Nil => 0
case x::xt => 1 + length (xt)
Ignore head of list with wildcard
_
def length [X] (xs:List[X]) : Int = xs match
case Nil => 0
case _::xt => 1 + length (xt)
Type and value parameters
def length [X] (xs:List[X]) : Int = ...
val xs1 = List(11,21,31)
val xs2 = List("a","b","c")
val len1 = length[Int](xs1)
val len2 = length[String](xs2)
-
X
is a type parameter
-
Type parameters in square brackets
-
xs
is a value parameter
-
Value parameters in round brackets (parentheses)
-
Types before values
-
Types usually inferred for function call
val len1 = length(xs1)
val len2 = length(xs2)
Reasoning
Evaluate step-by-step
length (List (1, 2, 3))
--> length (1::(2::(3::Nil)))
--> 1 + length (2::(3::Nil))
--> 1 + (1 + length (3::Nil))
--> 1 + (1 + (1 + length (Nil)))
--> 1 + (1 + (1 + 0))
--> 1 + (1 + 1)
--> 1 + 2
--> 3
def length (xs:List[Int]) : Int = xs match
case Nil => 0
case _::xt => 1 + length (xt)
The expression is the state of the computation
Appending Lists
Evaluate step-by-step
append (1::(2::Nil), 3::Nil)
--> 1::(append (2::Nil, 3::Nil)) // x = 1, xt = 2::Nil
--> 1::(2::(append (Nil, 3::Nil))) // x = 2, xt = Nil
--> 1::(2::(3::Nil)) // x = 2, xt = Nil
def append [X] (xs:List[X], ys:List[X]) : List[X] = xs match
case Nil => ys
case x::xt => x::(append (xt, ys))
-
Cons cells created with
1
and 2
in head
-
Cons cell
(3::Nil)
is reused (shared)
Appending Lists
-
Join two lists with
append
-
A new list is returned
-
The two lists are not modified
-
But the second list is shared!
def append [X] (xs:List[X], ys:List[X]) : List[X] = xs match
case Nil => ys
case x::xt => x::(append (xt, ys))
The expression is the state of the computation
Appending Lists
-
List
class has builtin method :::
scala> ((1 to 5).toList) ::: ((10 to 15).toList)
res1: List[Int] = List(1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15)
Unknown Methods
def f [X] (xs:List[X]) : List[X] = xs match
case Nil => Nil
case x::xt => f (xt) ::: List (x)
f (3::Nil)
--> f (Nil) ::: List (3)
--> Nil ::: List (3)
--> List (3)
f (2::(3::Nil))
--> f (3::Nil) ::: List (2)
--> List (3) ::: List (2)
--> List (3, 2)
f (1::(2::(3::Nil)))
--> f (2::(3::Nil)) ::: List (1)
--> List (3, 2) ::: List (1)
--> List (3, 2, 1)
CSC447 Concepts of Programming Languages Scala Introduction Instructor: James Riely