CS-A1120 Programming 2
Department of Computer Science
Aalto University
We have learned the basics of a computer, but
Getting from a high-level language (Scala, C++, Rust, …) to machine code?
var (a variable) and val (a value)
var is mutableval is immutableList, ArrayBuffer, …) also come in two flavoursSeq(2,0,2,4) you will get (some) immutable sequenceArray is a special collection, and always mutable (corresponds to Java arrays)
import scala.collection.mutable.ArrayBuffer val x = ArrayBuffer("a","b","c") val y = x println(s"x = $x") println(s"y = $y") x(0) = "CHANGED" println(s"x = $x") println(s"y = $y")
will print:
x = ArrayBuffer(a, b, c) y = ArrayBuffer(a, b, c) x = ArrayBuffer(CHANGED, b, c) y = ArrayBuffer(CHANGED, b, c)
val y = xdoes NOT create a copy of theArrayBufferobject, it simply creates a copy of the reference.
(There are also data structures called stacks and heaps, we will return to them in later courses.)
Scala supports both imperative and functional programming styles
val v = Vector(1.0, 2.5, 3.0) var i = 0 var sumOfSquares = 0.0 while i < v.length do sumOfSquares += v(i)*v(i) i=i+1
val v = Vector(1.0, 2.5, 3.0) val sumOfSquares = v.map(x=>x*x).sum
val
val v = Vector(1.0, 2.5, 3.0) // In the following, // map is a higher-order function // taking the anonymous function // x=>x*x as a parameter val squares = v.map(x=>x*x) val sumOfSquares = squares.sum
Functional programming does not influence what we can program, but how we program it.
(Also known as lambda functions, lambdas, or function literals)
Why? Sometimes the function is only needed once
val v = Vector(3,4,5,2,8,7) val evens = v.filter(x => x%2 == 0)
(parameter) => (function body),
x => x%2 == 0
Scala also allows for a simplified _ notation:
val v = Vector(3,4,5,2,8,7) val evens = v.filter(_%2 == 0)
Anonymous functions are values, and can be assigned to val and var:
val isEven = (x: Int) => (x%2 == 0) val v = Vector(3,4,5,2,8,7) val evens = v.filter(isEven)
In the Scala REPL:
scala> val isEven = (x: Int) => (x%2 == 0) val isEven: Int => Boolean = Lambda$2155/0x00000008409b1040@418ee41b
isEven: Int => BooleanA => B denotes a function typeInt => Boolean means that the val holds a reference to a function which takes an Int and returns a Boolean
filter method in Seq is higher order, as we have seen
def filter(pred: (A) => Boolean): Seq[A]pred then uses that to determine which elements to filterFor example:
scala> Seq(1,2,3).filter(x => x % 2 == 0) val res13: Seq[Int] = List(2)
x => x%2 == 0 as predWhat is this sorcery? How can functions (=program code) be given as a parameter to another function?
Well, if there could be a type Int, of some parameter, why couldn't there be one of type Int => Bool?
Function1 for single argument functions, so we could do:object isEven extends Function1[Int,Boolean]: def apply(x: Int): Boolean = (x % 2 == 0) end isEven val v = Vector(3,4,5,2,8,7) val evens = v.filter(isEven)
(The construction val isEven: Int => Boolean = x=>x%2 == 0 is 'syntactic sugar'.)
scala.collection offers many functional utility methodssplitAt, keys, filter, toSet, toMap, toVector, indexWhere, take, min, and max (where they are available)A function has side effects if it does anything else than take a value and return a result (computed somehow).
For example, it has side effects if it:
(The fact that execution takes time and uses memory is not considered a side effect.)
println, has side effects, because it prints a string to a console.def f(a: Int, b: Int): Int = a+b is side effects free, because it simply calculates a value.However,
def g(a: Int, b: Int): Int = println(s"a = $a, b = $b") a+b
has side effects. (Why?)
Changing the state of something mutable inside the function is also considered a side effect:
var x = 0 def h(v: Int): Int = if v > x then x = v x
has a side effect (Why?)
Updating a value on return is not considered a side effect
var x = 0 x = if v > x then v else x
does not have side effects
def printDivisibleBy3(S: Seq[Int]): Unit = S.filter(x=> (x % 3) == 0).foreach(x=>println(x))
def cSum(A : Array[Int]): Array[Int] = val B: Array[Int] = A var i = 1 while i < A.length do B(i) = B(i-1) + A(i) i += 1 B
def zipWithReverse(S: Seq[Int]): Seq[(Int,Int)] = S.zip(S.reverse)
import scala.util.Random def addNoise(S: Seq[Double]): Seq[Double] = S.map(x => x + Random.nextGaussian())
https://presemo.aalto.fi/prog2
A function is said to be pure if
- It is does not have side effects, and
- evaluating it with the same argument, always gives the same result
Are the following functions pure, and if not why?
def f(a: Int, b: Int) = if a > b then a else bprintlnRandom.nextGaussianjava.time.LocalDateTime.nowhttps://presemo.aalto.fi/prog2
In the following (slow) test for prime numbers:
def isPrimeSlow(n: Int): Boolean = require(n > 1) (2 until n).forall(divisor => n % divisor != 0)
divisor => n % divisor != 0, does not have n as a parameter
n is a free variable in the closure of the anonymous functionprefixPrintln that prints a string but inserts a line number and a user-configurable prefix first.prefixPrintln("Hello") should print [1]A>Hello, and if we made the same call again in the same session, we would expect [2]A>Hello.
// These var:s exist in the global scope // [for the sake of demo, in general a bad idea] var prefix = "A>" var lines = 0 // println on the format [<lines>]<prefix><s> def prefixPrintln(s: String) = lines +=1 println(s"[$lines]$prefix$s")
scala> prefixPrintln("Hello") [1]A>Hello scala> prefixPrintln("World") [2]A>World
var's declared in the global scope
scala> prefix = "B>" prefix: String = B> scala> lines = 4324324 lines: Int = 4324324 scala> prefixPrintln("Test") [4324325]B>Test
prefix or lines changes how the function behavesvar is a bad idea
prefix is given as parameter and lines is encapsulatedclass PrefixPrinter(prefix: String): private var lines = 0L // apply method lets us call objects using () def apply(s: String) = lines += 1 println(s"[$lines]$prefix$s")
scala> val prefixPrintln = PrefixPrinter("A>") val prefixPrintln: PrefixPrinter = PrefixPrinter@7be7c052 scala> prefixPrintln("Hello") [1]A>Hello scala> prefixPrintln("World") [2]A>World
This works fine, but we can achieve the same thing using only functions
lines and prefix be in the closure of a function that we return from a factory function:// Returns a println like function that prefixes everything // with [n]`prefix`, where n is the line number, // starting at 1 def createPrefixPrint(prefix: String): String => Unit = var lines = 0 s => // s is the parameter lines+=1 // first increase println(s"[$lines]$prefix$s") // then print
scala> val prefixPrintln = createPrefixPrint("A>") val prefixPrintln: String => Unit = Lambda$1682/0x0000000840835040@25e796fe scala> prefixPrintln("Hello") [1]A>Hello scala> prefixPrintln("World") [2]A>World
createPrefixPrint is the function String => Unit
String and returns Unit (nothing)Unit is that this is the return type of printlns => which is of type String => UnitMultiple parameters to a Scala function is usually given as a tuple. For example:
def sumTwo(a:Int, b:Int): Int = a + b
sumTwo(5,6)But, we can also use multiple parameter lists to do the same thing:
def sumTwo(a:Int)(b:Int): Int = a + b
sumTwo(5)(6)foldLeft method found in any iterable Scala collection.def sumTwo(a:Int, b:Int): Int corresponds to \(\mbox{sumTwo} : \mbox{Int} \times \mbox{Int} \mapsto \mbox{Int}\)def sumTwo(a:Int)(b:Int): Int corresponds to \(\mbox{sumTwo} : \mbox{Int} \mapsto \left( \mbox{Int} \mapsto \mbox{Int} \right) \)curried which converts a function of multiple parameters to a sequence of lambda functionsscala> def sumTwo(a:Int, b:Int): Int = a + b def sumTwo(a: Int, b: Int): Int scala> val sumTwoCurry = sumTwo.curried val sumTwoCurry: Int => Int => Int = scala.Function2$$Lambda$1815/0x0000000840899840@225ac5e7 scala> sumTwoCurry(10)(11) val res0: Int = 21
For example, given def sumTwo(a:Int)(b:Int): Int = a + b
a, to some value (for example 5)scala> val fivePlus = sumTwo(5) val fivePlus: Int => Int = $Lambda$1348/0x00000008406e6840@5381cdb6
b: Int => 5 + b.
Here is fivePlus used in the REPL:
scala> fivePlus(3) val res: Int = 8 scala> fivePlus(7) val res: Int = 12
Assume we define the following to concatenate strings:
def conc(x: String)(y: String): String = x + y val partial = conc("A")
What is the type of partial?
What is the result of running partial("B")?
What is the result of running partial("C") + partial("B")?
https://presemo.aalto.fi/prog2
Scala (and many other languages) has more than one way of evaluating arguments given to functions
A parameter can be made 'call-by-name' by using => first in the type definition. Let's create another function that prints a string with a prefix. Let's have it use multiple parameter lists, one for the prefix and another for the message string. Moreover, make the prefix call-by-name:
// prefix is call-by-name, and will be evaluated when needed (when it is printed) // msg is call-by-value and stays the same as when when logMessage is called def logMessage(prefix: => String)(msg: String): Unit = println(s"$prefix$msg")
Note the parameter prefix has the type => String.
prefix: => String)
// Import now, which gives the current time import java.time.LocalDateTime.now // Log message function, note that prefix is call-by-name def logMessage(prefix: => String)(msg: String): Unit = println(s"$prefix$msg") // Create a log function that prefix everything // with the current time val logThis = logMessage(now().toString + ": ") // Use it to log a few things logThis("Captain's log") logThis("Star-date...") logThis("I can't remember!")
Output:
2025-03-29T16:49:40.486410873: Captain's log 2025-03-29T16:49:40.486757394: Star-date... 2025-03-29T16:49:40.486890486: I can't remember!
prefix is evaluated each time it is needed by println
prefix: String)
// Import now, which gives the current time import java.time.LocalDateTime.now // Log message function, note that prefix is call-by-value def logMessage(prefix: String)(msg: String): Unit = println(s"$prefix$msg") // Create a log function that prefix everything // with the current time val logThis = logMessage(now().toString + ": ") // Use it to log a few things logThis("Captain's log") logThis("Star-date...") logThis("I can't remember!")
Output:
2025-03-29T16:55:24.388120025: Captain's log 2025-03-29T16:55:24.388120025: Star-date... 2025-03-29T16:55:24.388120025: I can't remember!
prefix is evaluated once, which is when logThis is created
Iterator is a mutable object
next(): AhasNext: Booleaniterator method on a Scala collection to get oneIterators are useful when we want to
Getting an iterator and asking for next() :
val s = Seq("Hey","You") // Make sequence val it = s.iterator // Get iterator // Access one by one println(it.next()) // "Hey" println(it.next()) // "You" println(it.next()) // This thrown an Exception
Note: java.util.NoSuchElementException
In a loop as long as hasNext is true :
val s = Seq("Hey","You") // Make sequence val it = s.iterator // Get iterator // Access one by one, as long as there are more while it.hasNext do println(it.next())
Can use the 'normal' collection methods :
val s = Seq(1,2,3,4) // Sum all even numbers s.iterator.filter(_%2 == 0).sum
Why bother? :
// Sum all even numbers in range val r =(1L to 100000000L) // Will give java.lang.OutOfMemoryError r.filter(_%2 ==0).sum
Note: java.lang.OutOfMemoryError: Java heap space
The same with iterator :
// Sum all even numbers in range val r =(1L to 100000000L) r.iterator.filter(_%2 ==0).sum // Will give 2500000050000000
Iterator trait, so we must specify the two methodsnext(): A(). This used in Scala for methods that does not take parameters, but has side-effectshasNext: Booleannext element to getLet us make an Iterator that calculates a numerical sequence ('orbit') using the following rule:
Take a positive integer.
If the number is even divide it by two, if it is odd then triple it and add one. Repeat.
More formally: given some \(x_0 \in \mathbb{Z}^{+}\), define
Iterator that gives every value in the sequence until it reaches 1 (which we'll exclude for brevity).Given some \(x_0 \in \mathbb{Z}^{+}\), define \(x_{n+1} =\begin{cases}x_n/2 & \mbox{if $x_n$ is even}\\3 x_n + 1 & \mbox{if $x_n$ is odd}\\\end{cases}\)
// A function which returns a new Iterator // representing the sequence of numbers // starting at `start` and ending when // the sequence reaches 1 (not included) def orbitIterator(start: BigInt) : Iterator[BigInt] = require(start>0, "Must start from a positive value") // new is used to create a new object new Iterator[BigInt]: //`x` is the current value, initially `start` private var x = start // Our implementation of `hasNext` // Orbit ends if we reach 1 def hasNext: Boolean = (x != 1) // Our impmenentation of `next()` // Calculated according to rule def next(): BigInt = // Temporarily hold the current value val result = x // Update current number x = if x%2 == 0 then x/2 else 3*x+1 // Return previous value result
// Let's test: // Explicitly with while val o5 = orbitIterator(5) while o5.hasNext do println(o5.next())
// Or the compactly with for for x <- orbitIterator(5) do println(x)
Results (from either call):
5 16 8 4 2
Assume we define a function to create an iterator over the bits in a Byte:
def bitsIt(w: Byte): Iterator[Boolean] = new Iterator[Boolean]: private var i: Int = 8 def hasNext: Boolean = (i > 0) def next(): Boolean = i = i - 1 (((w>>>i) & 0x1) == 0x1)
Now, if we use bitsIt as
val w: Byte = 7 var n = 0 for b <- bitsIt(w) do if b then n+=1
What is the value in n after the loop finishes?
Common and uniform framework for working with data
Vector, List, ArraySeq,…Set, SortedSet, BitSet,…Map, HashMap, ListMap, …
scala> import scala.collection.immutable._ import scala.collection.immutable._ scala> Vector(1,2,3,4).sum val res1: Int = 10 scala> List(1,2,3,4).sum val res2: Int = 10 scala> ArraySeq(1,2,3,4).sum val res4: Int = 10
Seems to do the same thing in the case of sum, at least, what is different?
Map is an abstract trait, while HashMap and ListMap are two different concrete classes implementing it
scala> import scala.collection.immutable._ import scala.collection.immutable.* scala> val m = Map("a"->1, "b"->2, "c"->8) // Getting a Map, but which kind? val m: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2, c -> 8)
But which concrete class is actually used?
(Unless you explicitly create one with new)
scala> m.getClass // As created on previous slide val res1: Class[? <: Map[String, Int]] = class scala.collection.immutable.Map$Map3
A class called Map3, OK, is this always the case?
Map("a"-> 1, "b"-> 2, "c"-> 8, "f"-> 9, "k"-> -1).getClass val res2: Class[? <: Map[String, Int]] = class scala.collection.immutable.HashMap
(Apparently not, why?)
ListBuffer and ArrayBuffer both implement Buffer, but
ListBuffer is fast to add an element at the beginning (and at the end), but 'slow' to indexArrayBuffer is fast to index, but 'slow' if you need to add an element to the beginning
scala.collection.mutable are mutable - they can be changed (updated) after creation.
scala> val x = scala.collection.mutable.Seq(1,2,3,4) val x: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 2, 3, 4) scala> x(1) = 7 // We can change value at index 1 scala> x // Look, updated! val res1: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 7, 3, 4)
Classes in scala.collection.immutable are immutable - they cannot be updated after creation
scala> val y = scala.collection.immutable.Seq(1,2,3,4) val y: Seq[Int] = List(1, 2, 3, 4) scala> y(1) = 7 // This won't work, look: ^ error: value update is not a member of Seq[Int] did you mean updated? scala> val z = y.updated(1,7) // Can update index 1 to 7, but val z: Seq[Int] = List(1, 7, 3, 4) // `updated` returns a new object scala> y // Look, y is still the same val res1: Seq[Int] = List(1, 2, 3, 4)
var, Array)
_._1 and friendsFor example, swapping elements
scala> val l = List(("apple",3.5), ("banana", 2.1), ("orange", 2.5)) val l: List[(String, Double)] = List((apple,3.5), (banana,2.1), (orange,2.5)) scala> l.map((x,y) => (y,x)) // Swap positions val res0: List[(Double, String)] = List((3.5,apple), (2.1,banana), (2.5,orange)) scala> l.map((_,x) => x).sum // Sum of second elements val res1: Double = 8.1
Previous syntax will work with basic tuples. For nested tuples a case expression is necessary:
// Zip previous list with its index scala> val dataIndex = l.zipWithIndex val dataIndex: List[((String, Double), Int)] = List(((apple,3.5),0), ((banana,2.1),1), ((orange,2.5),2)) // Pick out indices of values with p < 3; note the { case } scala> dataIndex.filter{case ((n,p),i) => p < 3}.map((_,i)=>i) val res3: List[Int] = List(1,2)
unapply methodscala> case class Person (fname:String, lname:String) scala> val people = Seq(Person("Gwen", "Stacy"), Person("Miles", "Morales"), Person("Peter", "Parker")) scala> people.map{case Person(_,last) => last} val res11: Seq[String] = List(Stacy, Morales, Parker)
scala> people.filter{case Person(f,l) => f.head != l.head}