CSC447

Concepts of Programming Languages

Scala Introduction

Instructor: James Riely

Scala

Scala

  • Scala has a REPL like Scheme
  • Boolean, numeric, and string Literals as in Java
    
    false || true
                  
    
    1 + 2
                  
    
    ("hello" + " " + "world").length
                  
  • Use of Java's libraries
    
    val dir = java.io.File ("/tmp")
    dir.listFiles.filter (f => f.isDirectory && f.getName.startsWith ("c"))
              
  • With explicit nulls enabled:
    
    val dir = java.io.File ("/tmp")
    dir.listFiles.nn.map(_.nn).filter (f => f.isDirectory && f.getName.nn.startsWith ("c"))
              

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
                  

Scala Type Checking

  • Scala performs static type checking
    
    def f () = 5 - "hello"   // rejected by type checker
                  
  • 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

Mutable Variables

Java

int x = 10;        // declare and initialize x
x = 11;            // assignment to x OK
            
C

int x = 10;        // declare and initialize x
x = 11;            // assignment to x OK
            
Scala

var x = 10         // declare and initialize x
x = 11             // assignment to x OK
            

Immutable Variables

Java

final int x = 10;  // declare and initialize x
x = 11;            // assignment to x fails
            

Final.java:4: error: cannot assign a value to final variable x
            
C

const int x = 10;  // declare and initialize x
x = 11;            // assignment to x fails
            

final.c:6:3: error: assignment of read-only variable ‘x’
            
Scala

val x = 10         // declare and initialize x
x = 11             // assignment to x fails
            

final.scala:3: error: reassignment to val
            

Expression Oriented

C comma expressions

(e_1, e_2, ..., e_n)
            
Scheme begin expressions

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

  • Parameters require type annotations
    
    def plus (x:Int, y:Int) : Int = x + y
                  
  • Return types
    • can often be inferred
    • but are required for recursive methods
  • Body is an expression; its value is returned

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; def is non-strict

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
}          

Structured Data

  • Tuples
    • Fixed number of heterogeneous items
  • Lists
    • Variable number of homogeneous items
  • Immutable and mutable variants
  • Pattern matching

Mutable Lists, Etc.

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); // mutating list OK
    xs = new List<> ();                 // reassignment fails
                  
  • Scala immutable linked list with var variable
    
    var xs = List (4, 5, 6)
    xs = 0 :: xs            // reassignment OK
    xs (1) = 7              // mutating list fails    
                  

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"); // infer type params
    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)) = 
  if p==null then throw MatchError(p)
  val x = p(0)
  val y = p(1)
  x + y
            

Immutable Linked Lists

  • Scala's :: is an infix cons operator
Linked List  with four elements: 11, 21, 31, 41
Scheme

(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         // right associative
            

Immutable Linked Lists

  • Constructors for linked lists
Scheme

(list 1 2 (+ 1 2))
            
Scala

List (1, 2, 1 + 2)
            

Immutable Linked Lists

  • Projection called head and tail in many PLs
Scheme

(car xs)
(cdr xs)
            
Scala

xs.head
xs.tail
            

Pattern Matching Behavior

The behavior of pattern matching...

def f(xs: List[Int]) = xs match 
  case Nil   => "List is empty"
  case y::ys => "List is non-empty, head is %d".format (y)
            
...branches and binds pattern variables

def g(xs: List[Int]) = 
  if xs == Nil then "List is empty" 
  else if xs.isInstanceOf[::[Int]] then 
    val zs = xs.asInstanceOf[::[Int]]
    val y : Int = zs.head
    val ys : List[Int] = zs.tail
    "List is non-empty, head is %d".format (y)
  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 y::ys => false
          

def head (xs:List[Int]) : Int = xs match 
  case Nil   => throw NoSuchElementException ()
  case y::ys => y
          

def tail (xs:List[Int]) : List[Int] = xs match 
  case Nil   => throw NoSuchElementException ()
  case y::ys => ys
          

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 y::ys => 1 + length (ys)
            
With parametric polymorphism

def length [X] (xs:List[X]) : Int = xs match 
  case Nil   => 0
  case y::ys => 1 + length (ys)
            
Ignore head of list with wildcard _

def length [X] (xs:List[X]) : Int = xs match 
  case Nil   => 0
  case _::ys => 1 + length (ys)
            

Reasoning

Evaluate step-by-step

length (List (1, 2, 3))
--> length (1::(2::(3::Nil)))
--> 1 + length (2::(3::Nil))      // y = 1, ys = 2::(3::Nil)
--> 1 + (1 + length (3::Nil))     // y = 2, ys = 3::Nil
--> 1 + (1 + (1 + length (Nil)))  // y = 3, ys = Nil
--> 1 + (1 + (1 + 0))
--> 1 + (1 + 1)
--> 1 + 2
--> 3
            

def length (xs:List[Int]) : Int = xs match 
  case Nil   => 0
  case y::ys => 1 + length (ys)
          
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))    // z = 1, zs = 2::Nil
--> 1::(2::(append (Nil, 3::Nil)))  // z = 2, zs = Nil
--> 1::(2::(3::Nil))                // z = 2, zs = Nil
            

def append [X] (xs:List[X], ys:List[X]) : List[X] = xs match 
  case Nil   => ys
  case z::zs => z::(append (zs, 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 z::zs => z::(append (zs, ys))
          
The expression is the state of the computation

Scheme Comparison

Scala

def append [X] (xs:List[X], ys:List[X]) : List[X] = xs match 
  case Nil   => ys
  case z::zs => z::(append (zs, ys))
            
Scheme

(define (append xs ys) 
  (if (equal? xs ()) 
      ys 
      (cons (car xs) (append (cdr xs) ys))))
            

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 y::ys => f (ys) ::: List (y)
          
What does f do?

f (Nil)
--> Nil
              

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)
              
Conclusion: f is reverse