We will go through several labs to show the fundamentals of Scala.
Prerequisites:
- Docker
- docker pull sakethkollu/intro_to_scala:latest (340 MB) (ubuntu)
- alternatively use the alpine image (200 MB) (BETA)
- or use
make getImage
- or use
make getImageAlpine
- IDE to edit files:
- MS Visual Studio Code
- Install the Scala Syntax Plugin for MSVSC
- Atom.io - popular opensoure IDE
- MS Visual Studio Code
- Google Slides
Logistics:
- Please work in the directory
todo
- This directory is a direct clone of
solutions
except it contains all necessary skeleton code and files as required by the lab
- This directory is a direct clone of
- The Autograder is merely a sanity check and does not cover all edge cases.
- Prepend
sol_
to any make command to run the solution version of the lab.
A number of examples have been taken from the following sources:
- http://twitter.github.io/scala_school/basics.html
- Programming in Scala by Odersky and Venners
- Make sure docker is installed
- Pull docker image using:
make getImage #or use docker pull sakethkollu/intro_to_scala:latest
- Clone or pull the latest
hackathon
repo and run all commands unless otherwise specified in thehackathon\introToScala
directory. - Start the docker image with:
#Make sure to run this in your hackathon\introToScala directory since -v ${PWD} mounts the present working directory as a volume in the container docker run --rm -it --privileged -v ${PWD}:/introToScala sakethkollu/intro_to_scala #or use make startContainer #or use make startContainerAlpine #for the Alpine image
- cd into the
introToScala
directory from the bash terminal for your docker container and you will see all the files for the lab in thereC:\Users\saket\Documents\hackathon\introToScala> docker run --rm -it --privileged -v ${PWD}:/introToScala sakethkollu/intro_to_scala root@f82a864f0d0e:/# ls bin boot dev etc home introToScala lib lib64 media mnt opt proc root run sbin srv sys tmp usr var root@f82a864f0d0e:/# cd introToScala root@f82a864f0d0e:/introToScala# ls README.md makefile solutions sources todo
- Alternatively just figure out how to install scala on your machine. Debian based OS just need
apt-get install scala
andapt-get install make
Test if Scala is installed by typing the following in your terminal:
scala -version
Start the Scala interpreter by typing:
scala
scala> 1+1
res0: Int = 2
Your expression is evaluated according to REPL and the result is stored to a default value of res0
. Try entering another expression and this time the result will be stored to res1
. In addition to storing the result, the interpreter will show you what type the result is.
Values in Scala are Immutable:
scala> val two = 1 + 1
two: Int = 2
scala> two = 2
<console>:12: error: reassignment to val
two = 2
val
is like the final keyword in Java
Variables in Scala are Mutable:
scala> var two = 1 + 1
two: Int = 2
scala> two = 2
two: Int = 2
You can assign any type to var
s and val
s:
scala> var name = "Saketh"
name: String = Saketh
scala> var name = "Saketh Kollu"
name: String = Saketh Kollu
scala> def addOne(m : Int): Int = m + 1
addOne: (m: Int)Int
scala> val three = addOne(2)
three: Int = 3
Note: Function declaration follows the following format.
Hit enter
after every line. The Scala interpreter won't execute the expression unless it is complete.
scala> def timesTwo(x: Int): Int = {
| val i = x + x
| i
| }
timesTwo: (x: Int)Int
scala> val four = timesTwo(2)
four: Int = 4
Note: It is common not to use the return
keyword in functions in Scala. The last line of the function that is executed is returned.
These are basically just lambda functions, functions that dont have a name binding associated with it.
scala> (i: Int) => i * 2
res2: Int => Int = <function1>
scala> res2(1)
res3: Int = 2
Note: work on this in introToScala/todo/lab0/
Open your favorite IDE and create a file called HelloWorld.scala
and enter the following:
object HelloWorld extends App {
println("Hello, world!")
}
We are creating a Scala object named HelloWorld
that will print "Hello, World!"
Note: Dont worry about the object syntax, we will go over that later
Having HelloWorld
extend App
at a high level tells Scala that the entry point of our program is here and will execute everything in the body of our object.
Alternatively we could write:
object HelloWorld {
def main(args: Array[String]): Unit = {
println("Hello, world!")
}
}
The entry point of the program will be the main method since we have a main method.
Note: Unit
is the keyword for the Null
entity in Scala. println
does not actually return anything besides printing to the terminal, so this function technically returns Unit
which must be specified.
To compile our Scala object into a .class
file, we use scalac
:
Note: Make sure you are in the introToScala/todo/lab0
directory.
scalac HelloWorld.scala
Run the file with scala
scala HelloWorld
cat: /release: No such file or directory
Hello, world!
You can also compile and run using the makefile!
From the introToScala
directory run the following commands:
make compileLab0
make runLab0
This command does both the above:
make lab0
Use this command to remove all .class files in introToScala/todo
:
make clean
You should see something like this:
sakethkollu@sakethsurface:~/Documents/hackathon/introToScala$ make runLab0
scala -cp ./todo/lab0/classes HelloWorld
cat: /release: No such file or directory
Hello, world!
Note: You might see an error like that above, but it just means your Java Home has some weird setup. Ignore it.
if
statements follow the same format as Java if
statements:
if (Boolean Expression) {
//If Expression is True
} else {
//If Expression is False
}
Scala for
loops are a bit different from Java for
loops. They are most analogous to python for
loops.
for(w <- 0 to 5){
// Execute once per each w
println(w)
}
The output of the above code is:
0
1
2
3
4
5
Note: the to
operation returns an iterator on the first and second arguments inclusive of both end points.
while
loops follow the same format as Java while
loops:
while (Boolean Expression) {
//Execute while Expression is True
}
Match on a value, returns the content after the =>
:
val times = 1
times match {
case 1 => "one"
case 2 => "two"
case _ => "some other number"
}
You can use if statements to create "gaurds":
times match {
case i if i == 1 => "one"
case i if i == 2 => "two"
case _ => "some other number"
}
We capture the value times
in each of the i
variables. The _
case is a wildcard case to prevent runtime error if you do not get any matches.
You can go even further by specifying a type for the variable to by match
ed:
def bigger(o: Any): Any = {
o match {
case i: Int if i < 0 => i - 1
case i: Int => i + 1
case d: Double if d < 0.0 => d - 0.1
case d: Double => d + 0.1
case text: String => text + "s"
}
}
This is how you create a class. Implemented all methods marked TODO and any ____
Note: work on this in introToScala/todo/lab1/Car.scala
class Car(name: String){
val owner = name
val make : String = "Toyota"
val model: String = "Corolla"
val year: Int = 2020
val mpg: ____ = 31
var fuel: Double = 0.0
var tank: ____ = 13.2
def getOwner(): ____ = {
owner
}
def getMake(): ____ = {
____
}
//TODO: Implement getModel
//TODO: Implement getYear
//TODO: Implement getMPG
//TODO: Implement getFuel
//TODO: Implement setFuel(newFuel : Double): Unit
//Update fuel iff 0 <= newFuel <= tank
//TODO: Implement getTankCap: Return tank
//TODO: Implement fillMax(): Unit
//Set fuel to the tank capacity
//TODO: Implement drive(miles : Double) : Int
//Return 1 if drove and 0 if not drove
/*
Drive iff:
a) Remaining fuel is >= 0
b) miles > 0
Driving should update your fuel level
*/
}
From the introToScala
directory run the following commands:
make compileLab1_1
make runLab1_1
This command does both the above:
make lab1_1
Use this command to remove all .class files in introToScala/todo
make clean
Objects are used when you only want a single instance of something. Useful when you want a class factory.
object Timer {
var count = 0
def currentCount(): Long = {
count += 1
count
}
}
Usage:
scala> Timer.currentCount()
res0: Long = 1
Use classes with the same name as an object to create a companion object
that is often used for factories.
class Bar(foo: String)
object Bar {
def apply(foo: String) = new Bar(foo)
}
Preserve order, mutable and can contain duplicates. Access elements zero-indexed using parenthesis instead of square brackets.
scala> val numbers = Array(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
numbers: Array[Int] = Array(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
scala> numbers(3)
res0: Int = 4
scala> numbers(3) = 10
scala> numbers
res2: Array[Int] = Array(1, 2, 3, 10, 5, 1, 2, 3, 4, 5)
Note: Although numbers
is a val, which cannot change, it just means numbers
cannot be reassigned to a new array (even an array containing the same elements as numbers
. numbers
however can still have its elements changed.
Preserve order, can contain duplicates, however are immutable unlike arrays.
scala> val numbers = List(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
numbers: List[Int] = List(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
scala> numbers(3) = 10
<console>:9: error: value update is not a member of List[Int]
numbers(3) = 10
Note: The error message says the "value update
" is not a member of List[Int] which once again shows how Scala is object oriented at the core with the parenthesis acting as an operator that can be used to update (Not in the case of the List class though)
Do not preserve order nor do they contain duplicates. They are also immutable.
scala> val numbers = Set(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
numbers: scala.collection.immutable.Set[Int] = Set(5, 1, 2, 3, 4)
Logical collection of items without using a class or object. They are 1 indexed and accessed by tuple._#
:
scala> val hostPort = ("localhost", 80)
hostPort: (String, Int) = (localhost, 80)
scala> hostPort._1
res0: String = localhost
scala> hostPort._2
res1: Int = 80
hostPort match {
case ("localhost", port) => ...
case (host, port) => ...
}
#Useful(?) Syntax to make tuples of 2 values using ->
scala> 1 -> 2
res0: (Int, Int) = (1,2)
Works like how you expect it to, mapping one value to another. Note that Maps are essentially composed of tuples by the a -> b syntax above.
scala> val numbers = Map("one" -> 1, "two" -> 2)
numbers: scala.collection.immutable.Map[java.lang.String,Int] = Map(one -> 1, two -> 2)
scala> numbers.get("two")
res0: Option[Int] = Some(2)
scala> numbers.get("three")
res1: Option[Int] = None
Note: Maps return an option class that is essentially a wrapper container that may or may not hold something. This is in the event where the requested key does not exist. None
is a subclass of Option that is being returned in the last line above.
Evaluate a function over a list, returning a new list.
scala> val numbers = List(1, 2, 3, 4)
numbers: List[Int] = List(1, 2, 3, 4)
scala> numbers.map((i: Int) => i * 2) #Note that this is an anonymous function
res0: List[Int] = List(2, 4, 6, 8)
scala> numbers
res1: List[Int] = List(1, 2, 3, 4)
Exactly the same as map
but it returns nothing. Best for functions that have side-effects like println
scala> val doubled = numbers.foreach((i: Int) => i * 2)
doubled: Unit = ()
scala> doubled
scala> numbers.foreach(println)
1
2
3
4
Removes elements that fail to return true on the passed in filter function. Often called a predicate function.
scala> numbers.filter((i: Int) => i % 2 == 0)
res0: List[Int] = List(2, 4)
scala> def isEven(i: Int): Boolean = i % 2 == 0
isEven: (i: Int)Boolean
scala> numbers.filter(isEven)
res2: List[Int] = List(2, 4)
Note: work on this in introToScala/todo/lab2/Lab2.scala
object Lab2 {
//TODO: Return the nth prime number
def nthPrime( /* __________ */ ) : Int = {
}
//TODO: Find the missing number in the array.
//First and last numbers are garunteed to exist,
/*
scala> val numbers = List(1,2,3,5)
scala> missing(numbers)
res0: Int = 4
*/
def missing( /* __________ */ ) : Int {
}
}
Compile:
make compileLab2
Run:
make runPopulate
make runMissing
make runDuplicates
#run all with:
make runLab2
#Compile and Run
make lab2
Compute the word count of every word in the data file.
Note: work on this in introToScala/todo/lab2/WordCount.scala
import scala.io.Source
import java.io._
//Note: Scala FileIO is just Java FileIO and uses the Java library. It shows how versatile Scala is since it is interoperable with legacy Java code!
class WordCount {
def wordCount() : Map[String, Int] = {
val s1 = Source.fromFile("todo/lab2/data.txt").mkString; //returns the file data as String
//splitting String data with white space
val str = s1.split("\\s+") //Type is of Array
println(str) //remove in final code
//TODO: Count the occurences of every word in data.txt
counts
}
}
Compile:
make compileWordCount
Run:
make runWordCount
Compile and Run:
make wordCount
The following are recursion problems that you will solve in scala:
Note: work on this in introToScala/todo/lab3/Lab3.scala
//TODO: Find the greatest common divisor of two Integers
def gcd( /* __________ */ ) : Int = {
}
//TODO: Count the number of ways to make change of an integer quantity using
// coins of value 1 and 2 only
def countingChange( /* __________ */ ) : Int = {
}
scala> import scala.collection.mutable.LinkedList
import scala.collection.mutable.LinkedList
scala> val b = LinkedList(1)
b: scala.collection.mutable.LinkedList[Int] = LinkedList(1)
scala> b.next
res0: scala.collection.mutable.LinkedList[Int] = LinkedList()
scala> print(b)
LinkedList(1)
scala> b.head
res2: Int = 1
scala> b.next = LinkedList(2)
b.next: scala.collection.mutable.LinkedList[Int] = LinkedList(2)
scala> b
res3: scala.collection.mutable.LinkedList[Int] = LinkedList(1, 2)
scala> b.next.next = LinkedList(3)
b.next.next: scala.collection.mutable.LinkedList[Int] = LinkedList(3)
scala> b
res4: scala.collection.mutable.LinkedList[Int] = LinkedList(1, 2, 3)
Note: You might need to import LinkedList using collection.mutable.LinkedList as above. Stick it at the top of Lab3.scala
//TODO: Merge two sorted LinkedLists, returning a new LinkedList
def mergeLinkedLists( /* __________ */ ) : LinkedList = {
}
Compile:
make compileLab3
Run:
make runGCD
make runCountingChange
make runMergeLL
#Run all
make runLab3
#Compile and Run
make lab3
I dont know yet either! Check out the sources listed above!