Scala: Difference between revisions
(19 intermediate revisions by the same user not shown) | |||
Line 192: | Line 192: | ||
case1Free = 10 | case1Free = 10 | ||
case1Sum(80) // 90 | case1Sum(80) // 90 | ||
</syntaxhighlight> | |||
==Repeated Arguments (Params)== | |||
We can do this in Scala using the asterix. It has to be the last arguments. | |||
<syntaxhighlight lang="scala"> | |||
def lengthOfStringsR(strings: String*): | |||
Unit = strings foreach (s => println(s"$s -> ${s.length}")) | |||
</syntaxhighlight> | |||
==Named Parameters and Defaults== | |||
We can do this by providing the name of the parameters. | |||
<syntaxhighlight lang="scala"> | |||
def log(message: String, eventTime: LocalDateTime = LocalDateTime.now): Unit = { | |||
println(s"$eventTime -> $message") | |||
} | |||
log("Hello Martin!", LocalDateTime.of(2018, 6, 12, 0, 0, 0)) | |||
log("I am getting better with Scala!") | |||
</syntaxhighlight> | |||
==Tail Recursion== | |||
Given the function | |||
<syntaxhighlight lang="scala"> | |||
val n = 5 | |||
def sumR(n: Int): Int = { | |||
if(n == 1) 1 | |||
else n + sumR(n-1) | |||
} | |||
sumR(n) | |||
</syntaxhighlight> | |||
This creates many stack frames and therefore resources. With Tail Recursive we can specify the function to be applied and reduce the resources | |||
<syntaxhighlight lang="scala"> | |||
def sumTR(n: Int): Int = { | |||
@tailrec | |||
def go(currentNum: Int, totalSoFar: Int = 0): Int = { | |||
if(currentNum == 0) totalSoFar | |||
else go(currentNum - 1, totalSoFar + currentNum) | |||
} | |||
go(n) | |||
} | |||
sumTR(n) | |||
</syntaxhighlight> | |||
Quite liked this summary which show how the recursion can be separated from the functional code. | |||
<syntaxhighlight lang="scala"> | |||
int fac_times (int n, int acc) { | |||
if (n == 0) return acc; | |||
else return fac_times(n - 1, acc * n); | |||
} | |||
// Can we expressed was | |||
int fac_times (int n, int acc) { | |||
label: | |||
if (n == 0) return acc; | |||
acc *= n--; | |||
goto label; | |||
} | |||
</syntaxhighlight> | |||
And here is the Scala version | |||
<syntaxhighlight lang="scala"> | |||
def tailRecFactorial(n: Int): BigInt = { | |||
@tailrec | |||
def factorialHelper(x: Int, accumulator: BigInt): BigInt = { | |||
if (x <= 1) accumulator | |||
else factorialHelper(x - 1, x * accumulator) | |||
} | |||
factorialHelper(n, 1) | |||
} | |||
</syntaxhighlight> | |||
==Making your own Control Structure== | |||
In many languages we have control structures such as if and for. | |||
<syntaxhighlight lang="scala"> | |||
for(someCondition) { | |||
// process | |||
} | |||
</syntaxhighlight> | |||
We can make our own because we know how to pass an operation to a a function. | |||
<syntaxhighlight lang="scala"> | |||
// Our standard High Order Function | |||
def time(n: Int, operation: Int => Unit): Unit = { | |||
... | |||
} | |||
// We can change this to a curried function with | |||
def time(n: Int((operation: Int => Unit): Unit = { | |||
... | |||
} | |||
// We define our op | |||
val operation = (n: Int) => { | |||
Thread.sleep(1000) // introduced latency | |||
val numbers = (1 to n).toList | |||
println(s"Sum of first $n numbers is ${numbers.sum}") | |||
} | |||
// Call it with | |||
time(100)(operation) | |||
// or we can use curly braces as the function only takes one argument | |||
time(100) {operation} | |||
// Now we can define our own operation with | |||
time(1000000) { n: Int => | |||
val numbers = (1 to n).toList | |||
println(s"Sum of first $n numbers is ${numbers.sum}") | |||
} | |||
</syntaxhighlight> | |||
==By Named Parameter== | |||
This will result in compilation failure, since it needs a function that takes nothing and returns Boolean | |||
<syntaxhighlight lang="scala"> | |||
def assertTrue(predicate: () => Boolean): Boolean = predicate() | |||
assertTrue(() => 12 > 10) | |||
assertTrue(() => 12 + 34 - 12 > 30) | |||
// assertTrue(12 > 10) | |||
</syntaxhighlight> | |||
But this is OK | |||
<syntaxhighlight lang="scala"> | |||
def assertTrue(predicate: => Boolean): Boolean = predicate | |||
assertTrue(12 > 10) | |||
assertTrue(12 + 34 - 12 < 30) | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 292: | Line 410: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==Traits (Mixens)== | |||
Scala support Mixens which are essentially small bits of functionality define external to the class. E.g. | |||
<syntaxhighlight lang="scala"> | |||
trait Db { | |||
private var contents: Map[String, String] = Map.empty | |||
protected def save(key: String, value: String): Unit = contents += (key -> value) | |||
def get(key: String): Option[String] = contents.get(key) | |||
} | |||
class Bank extends Db { | |||
def openAccount(userId: String): String = { | |||
val accountId = "A-" + UUID.randomUUID() | |||
save(userId, accountId) | |||
accountId | |||
} | |||
def getAccount(userId: String): Option[String] = get(userId) | |||
} | |||
</syntaxhighlight> | |||
Note you can instantiate a trait too. | |||
<syntaxhighlight lang="scala"> | |||
class InMemoryDb extends Db | |||
var repo: Db = new InMemoryDb | |||
</syntaxhighlight> | |||
==Class Hierarchy== | ==Class Hierarchy== | ||
All classes inherit from Any. Below is the hierarchy. | All classes inherit from Any. Below is the hierarchy. | ||
Line 313: | Line 458: | ||
override def toString: String = "$" + amount | override def toString: String = "$" + amount | ||
} | } | ||
</syntaxhighlight> | |||
==Implicit Conversion== | |||
With the above example it would be nice not to have to construct a Dollar each time you have a numerical value. Like c++ and C# you can define implicit functions to do this | |||
<syntaxhighlight lang="scala"> | |||
case class Dollars(amount: Double) | |||
def withTax(dollars: Dollars, taxRate: Double) = | |||
Dollars(dollars.amount * (1 + taxRate)) | |||
withTax(Dollars(200), 0.10) | |||
// withTax(200.0, 0.30) // won't compile, needs Dollar | |||
// But add conversion | |||
implicit def doubleToDollars(d: Double) = Dollars(d) | |||
withTax(Dollars(200), 0.10) | |||
withTax(200.0, 0.30) | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 332: | Line 493: | ||
var y: Int = yc | var y: Int = yc | ||
def isEqual(obj: Any) = obj.isInstanceOf[Point] && obj.asInstanceOf[Point].x == y | def isEqual(obj: Any) = | ||
obj.isInstanceOf[Point] && | |||
obj.asInstanceOf[Point].x == y | |||
} | } | ||
Line 352: | Line 515: | ||
println("Fred Was Ere") | println("Fred Was Ere") | ||
} | } | ||
</syntaxhighlight> | |||
We can mix in more traits with the with keyword | |||
<syntaxhighlight lang="scala"> | |||
class MyClass extends OtherClass with Trait1 with Trait2 | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 556: | Line 723: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==Reduce, Fold and Scan== | |||
===Reduce=== | |||
Reduce applies an operator to the elements of the sequence. In the example below this would apply the operator + to each element giving the result 41. | |||
<syntaxhighlight lang="scala"> | |||
val lst = List(1,2,3,5,7,10,13); | |||
lst.reduceLeft(_+_) // or | |||
lst.reduceLeft( (x,y) => { | |||
println(x + " , "+ y); | |||
x +y; | |||
}) | |||
</syntaxhighlight> | |||
===Fold=== | |||
Fold allows you to pass the initial argument | |||
<syntaxhighlight lang="scala"> | |||
val lst = List(1,2,3,5,7,10,13); | |||
lst.foldLeft(100)(_+_) // 141 | |||
</syntaxhighlight> | |||
===Scan=== | |||
This is like Fold but will provide the result as a List | |||
<syntaxhighlight lang="scala"> | |||
val lst = List(1,2,3,5,7,10,13); | |||
lst.foldLeft(100)(_+_) // List(100, 101, 103, 106, 111, 118, 128, 141) | |||
</syntaxhighlight> | |||
===Right=== | |||
It should be noted that the right operation do not use recursion and can as such run out of resources. | |||
[[File:Scala Fold Left n Right.png|600px]] | |||
=Concurrency= | =Concurrency= | ||
==Concurrency and Parallelism== | ==Concurrency and Parallelism== |
Latest revision as of 10:29, 12 January 2021
Introduction
Some features of the language
- Functional and Object Orientated
- Use of immutable Data Structure
- Rich Collections Library
- Better Concurrency Support
To get this to work I downloaded scala and intellij. Some resources can be found at https://github.com/hhimanshu/scala-fundamentals
Functional Programming
Some definitions
- Immutability is when we can not allowed to change a variable. Not changing a value means parallelism will work better
- Expression are something yields a value. e.g. x+y and have not side effects
- Statements is code which does something. e.g. do(something) and have side effects
- Function is a a relation between a set of inputs and a set of outputs with the property that each input is related to exactly one output.
- Pure always has the same output, easier to test, parallelisation
- Impure may not have the same output
High Order Functions
High Order Functions are function which take functions as an argument
High Order functions allow you to provide some of the method as an argument to extract the part which is different
So given the following to get apples
def getApples(basket: List[Fruilt]) = getFruits(fruitBasket,
(fruit: Fruit) => fruit.name = "apple")
Can be replaced with a more generic
def getFruits(basket: List[Fruilt], filterByFruit: Fruit => Boolean) =
for (fruit <- basket if filterByFruit(fruit)) yield fruit
Scala provide many other HOFs not KnightRider such as
- map
- reduce
- filter
- fold
Types
val and var
Like Kotlin, scala supports val and var where only vars can be changed
Data Types
Data Type | Possible Values | |
---|---|---|
Boolean | true or false | |
Byte | 8-bit signed two’s complement integer (-2^7 to 2^7-1, inclusive) -128 to 127 | |
Short | 16-bit signed two’s complement integer (-2^15 to 2^15-1, inclusive) -32,768 to 32,767 | |
Int | 32-bit two’s complement integer (-2^31 to 2^31-1, inclusive) -2,147,483,648 to 2,147,483,647 | |
Long | 64-bit two’s complement integer (-2^63 to 2^63-1, inclusive) (-2^63 to 2^63-1, inclusive) | |
Float | 32-bit IEEE 754 single-precision float 1.40129846432481707e-45 to 3.40282346638528860e+38 | |
Double | 64-bit IEEE 754 double-precision float 4.94065645841246544e-324d to 1.79769313486231570e+308d | |
Char | 16-bit unsigned Unicode character (0 to 2^16-1, inclusive) 0 to 65,535 | |
String | a sequence of Char | |
Interpolation
There are three types of interpolation,
- String,
- Raw and
- f-style
String
println(s'Fred was ere $myVar')
println(s'Fred was ere ${myVar.attr}')
Raw
Struggled to understand this but I think it means you can escape string literals where required e.g. \\ evaluates to \
println(raw"Windows dir c:\\Program Files")
F-Styles
This is using the java formatter class
println(math.E) // 2.718281242356
println(f"${math.E}%.5f") // 2.71828
Conditions
If conditions are expressions and not statements. We generally do not need the return statement. I think this is like Ruby from memory e.g.
val arguments = Array("Monday")
val day = if (!arguments.isEmpty) arguments(0) else "Sunday"
Loops
Imperative
Where we do something
val letters = List("a","b","c","d","e")
for(letter <- letters) {
println(letter)
}
We can add filters on the end e.g. to print even numbers
for(letter <- letters if (number %2) == 0) {
println(letter)
}
And not just one, many, which I think is looking really really ugly
for(
letter <- letters
if (number %2) == 0)
if (number > 2)
{
println(letter)
}
We can iterate within an iteration though using the same approach e.g. we could have
for(number <- numbers) {
for(letter <- letters) {
println(number + " => " + letter)
}
}
But with the new way this could be written with curly braces
for{
number <- numbers
letter <- letters
} println(number + " => " + letter)
Functional
Where we return values. Using the example above we could yield a List[String] of values.
for{
number <- numbers
letter <- letters
} yield number + " => " + letter
Functions
Introduction
Functions are very similar to other languages
def foo(arg: Type): ReturnType = {
// Stuff
}
Local Function
We can embed function in function to hide them. Not sure this is the best exmaple.
class Calculator {
def sumOfSquares(a: Int, b: Int) = {
def square(n: Int): Int = math.pow(n, 2).intValue()
square(a) + square(b)
}
def multiplyDoubles(a: Int, b: Int) = {
def double(n: Int): Int = {
println(s"Parent received $a and $b")
2 * n
}
double(a) * double(b)
}
def divisionOfCPasubes(a: Int, b: Int) = {
def cube(n: Int): Int = math.pow(n, 3).intValue()
cube(a) / cube(b)
}
}
Partially Applied Functions
This is when we do not provide all of the arguments when declaring. These can be executed later with the missing values. Love the picture this time around.
And a good example too
val s5 = sum(_: Int, _: Int, 3)
val s6 = sum(1, _: Int, _: Int)
s5(1, 2)
s6(2, 3)
Closures
Closure differ from Functional Literals as there is a component which is used but not defined in the function. Hence Function Literal.
Example
If we change the value not part of the literal then this will effect the outcome of subsequent calls
// case 1: free variable changes after
// function value is created
var case1Free = 20
val case1Sum = (x: Int) => x + case1Free
case1Sum(80) // 100
// Change
case1Free = 10
case1Sum(80) // 90
Repeated Arguments (Params)
We can do this in Scala using the asterix. It has to be the last arguments.
def lengthOfStringsR(strings: String*):
Unit = strings foreach (s => println(s"$s -> ${s.length}"))
Named Parameters and Defaults
We can do this by providing the name of the parameters.
def log(message: String, eventTime: LocalDateTime = LocalDateTime.now): Unit = {
println(s"$eventTime -> $message")
}
log("Hello Martin!", LocalDateTime.of(2018, 6, 12, 0, 0, 0))
log("I am getting better with Scala!")
Tail Recursion
Given the function
val n = 5
def sumR(n: Int): Int = {
if(n == 1) 1
else n + sumR(n-1)
}
sumR(n)
This creates many stack frames and therefore resources. With Tail Recursive we can specify the function to be applied and reduce the resources
def sumTR(n: Int): Int = {
@tailrec
def go(currentNum: Int, totalSoFar: Int = 0): Int = {
if(currentNum == 0) totalSoFar
else go(currentNum - 1, totalSoFar + currentNum)
}
go(n)
}
sumTR(n)
Quite liked this summary which show how the recursion can be separated from the functional code.
int fac_times (int n, int acc) {
if (n == 0) return acc;
else return fac_times(n - 1, acc * n);
}
// Can we expressed was
int fac_times (int n, int acc) {
label:
if (n == 0) return acc;
acc *= n--;
goto label;
}
And here is the Scala version
def tailRecFactorial(n: Int): BigInt = {
@tailrec
def factorialHelper(x: Int, accumulator: BigInt): BigInt = {
if (x <= 1) accumulator
else factorialHelper(x - 1, x * accumulator)
}
factorialHelper(n, 1)
}
Making your own Control Structure
In many languages we have control structures such as if and for.
for(someCondition) {
// process
}
We can make our own because we know how to pass an operation to a a function.
// Our standard High Order Function
def time(n: Int, operation: Int => Unit): Unit = {
...
}
// We can change this to a curried function with
def time(n: Int((operation: Int => Unit): Unit = {
...
}
// We define our op
val operation = (n: Int) => {
Thread.sleep(1000) // introduced latency
val numbers = (1 to n).toList
println(s"Sum of first $n numbers is ${numbers.sum}")
}
// Call it with
time(100)(operation)
// or we can use curly braces as the function only takes one argument
time(100) {operation}
// Now we can define our own operation with
time(1000000) { n: Int =>
val numbers = (1 to n).toList
println(s"Sum of first $n numbers is ${numbers.sum}")
}
By Named Parameter
This will result in compilation failure, since it needs a function that takes nothing and returns Boolean
def assertTrue(predicate: () => Boolean): Boolean = predicate()
assertTrue(() => 12 > 10)
assertTrue(() => 12 + 34 - 12 > 30)
// assertTrue(12 > 10)
But this is OK
def assertTrue(predicate: => Boolean): Boolean = predicate
assertTrue(12 > 10)
assertTrue(12 + 34 - 12 < 30)
Anonymous Functions
Scala supports this e.g.
val plusOne = (x: Int) => x + 1
We can use the name function above with
plusOne(99)
Weird Option for Arrow Functions
If we use the argument in an arrow function once and only once we can drop replace the argument with an _ and drop the arrow. So
fruitBasket.filter(fruit => fruit.name)
// Can be replaced with
fruitBasket.filter(_.name)
Classes
Introduction
They can contain fields and methods like most languages. Like Kotlin we can initialize on the constructor
class Employee {
private var salary: Int = 100
def getSalary() = salary
def setSalary(newSalary) = {
salary = newSalary
}
}
var john = new Employee
Parameters in the constructor can become member variables and you can modify these if you use var instead of val ouch
class Employee(val first: String, val last: String) {
}
var john = new Employee
john.first
Companion Objects (Static Functions)
A bit like kotlin but a little different in that they are coded separate and can access the private fields of the class
object MathCompanion {
def sum(a: Int, b: Int): Int = a + b
def getPrivateMember: Int = new MathCompanion().max
}
class MathCompanion {
private val max = 100
}
Apply
We can add a special apply method to the companion object to allow construction without the new keyword. This provides a functional way to create an object I think like Ruby.
object MathCompanion {
def apply(firstname: String) = new Person(firstname)
}
class Person(firstname: String) {
...
}
val joe = Person("Fred")
Case Classes
This modifier
- Implements apply method
- Creates immutable arguments to the constructor
- Copy method to make modified copies
- Add Hash code, equals and toString()
- Pattern Matching
case class Course(title: String, author: String)
val scalaCourse = Course("Scala Test", "Joe Humanshu")
Abstract Classes
Scala support abstract classes e.g. For younger people maybe this is beginning of interfaces
abstract class Employee {
val first: String
val second: String
}
abstract class DepartmentEmployee extends Employee {
private val secret = "Big Secret!"
val department: String
val departmentCode: String
val numberOfStocks: Int
override def toString: String =
"[" + first + "," + last + "," + department + "," + numberOfStocks + "]"
}
class RnDEmployee(f: String, l: String) extends DepartmentEmployee {
val first = f
val last = l
val department = "Research and Development"
val departmentCode = "R&D"
val numberOfStocks = 100
}
Traits (Mixens)
Scala support Mixens which are essentially small bits of functionality define external to the class. E.g.
trait Db {
private var contents: Map[String, String] = Map.empty
protected def save(key: String, value: String): Unit = contents += (key -> value)
def get(key: String): Option[String] = contents.get(key)
}
class Bank extends Db {
def openAccount(userId: String): String = {
val accountId = "A-" + UUID.randomUUID()
save(userId, accountId)
accountId
}
def getAccount(userId: String): Option[String] = get(userId)
}
Note you can instantiate a trait too.
class InMemoryDb extends Db
var repo: Db = new InMemoryDb
Class Hierarchy
All classes inherit from Any. Below is the hierarchy.
Example Type
Below is an example of creating a type using the class Hierarchy. The companion object allows functional creation as it implement the apply method
package main.scala.com.h2.entities
object Dollars {
val Zero = new Dollars(0)
def apply(a: Int): Dollars = new Dollars(a)
}
class Dollars(val amount: Int) extends AnyVal with Ordered[Dollars] {
override def compare(that: Dollars): Int = amount - that.amount
def +(dollars: Dollars): Dollars = new Dollars(amount + dollars.amount)
def -(dollars: Dollars): Dollars = new Dollars(amount - dollars.amount)
override def toString: String = "$" + amount
}
Implicit Conversion
With the above example it would be nice not to have to construct a Dollar each time you have a numerical value. Like c++ and C# you can define implicit functions to do this
case class Dollars(amount: Double)
def withTax(dollars: Dollars, taxRate: Double) =
Dollars(dollars.amount * (1 + taxRate))
withTax(Dollars(200), 0.10)
// withTax(200.0, 0.30) // won't compile, needs Dollar
// But add conversion
implicit def doubleToDollars(d: Double) = Dollars(d)
withTax(Dollars(200), 0.10)
withTax(200.0, 0.30)
Traits and Main
A trait encapsulates method and field definitions, which can then be reused by mixing them into classes. Unlike class inheritance, in which each class must inherit from just one superclass, a class can mix in any number of traits.
Traits are used to define object types by specifying the signature of the supported methods. Scala also allows traits to be partially implemented but traits may not have constructor parameters.
A trait definition looks just like a class definition except that it uses the keyword trait. The following is the basic example syntax of trait.
trait Equal {
def isEqual(x: Any): Boolean
def isNotEqual(x: Any): Boolean = !isEqual(x)
}
class Point(xc: Int, yc: Int) extends Equal {
var x: Int = xc
var y: Int = yc
def isEqual(obj: Any) =
obj.isInstanceOf[Point] &&
obj.asInstanceOf[Point].x == y
}
object Demo {
def main(args: Array[String]) {
val p1 = new Point(2, 3)
val p2 = new Point(2, 4)
val p3 = new Point(3, 3)
println(p1.isNotEqual(p2))
println(p1.isNotEqual(p3))
println(p1.isNotEqual(2))
}
}
Whilst learning scala we are directed to use the App trait as it contains the main function e.g.
object Hello extends App {
println("Fred Was Ere")
}
We can mix in more traits with the with keyword
class MyClass extends OtherClass with Trait1 with Trait2
Preconditions
Introduction
Scala preconditions are a set of major functions that have different conditions a programmer must follow while designing a software. They are
- Assert General Assertions
- Assume State an Axiom
- Require Specifically checking inputs
- Ensuring is a post condition that has also been covered.
Example Assert
Assert method puts a necessity for a condition to be satisfied while performing a certain action. If this condition is satisfied, then the code works fine, otherwise it throws an exception.
// Code to check the age of the applicant
val applicant_age = 16
// assert method calling
assert(applicant_age>17)
Example Assume
If the assume condition is violated, the checker, silently leaves the path and doesn’t allow the program to go much deeper. The assume() method has the same syntax as the previously mentioned assert(), the only difference being its execution.
// Code to check the age of the applicant
license_approval(17)
// Method to approve the License application
def license_approval(applicant_age:Int) {
assume(applicant_age>=18)
Example Require
The following example shown a method to double any odd number. If the number passed in the function is odd, it works smoothly, if not, then an exception is thrown.
// Code to double the odd numbers
def double_odd_numbers(number:Int) : Int = {
require(number%2==1) // Checking if the number is odd using assume method
number * 2;
}
// Calling function
double_odd_numbers(13)
If the condition is not satisfied, the following exception is thrown:
Exception in thread "main" java.lang.IllegalArgumentException: requirement failed
Example Ensuring
This method is usually applied along with the require() method to make a workable program. Considering the above example itself, we can modify the code by adding an ensure condition which requires the input number to be less than a certain limit.
// Code to double the odd numbers
def double_odd_numbers(number:Int, lmt:Int) : Int = {
require(number%2==1) // Checking if the number is odd using assume method
number * 2;
} ensuring(number * 2 < lmt) // Ensuring that the number produced is less than the limit.
// Calling function
// The method also requires a limit [parameter to be passed.
double_odd_numbers(13, 100)
Error Handling
Null Handling
Maybe tired of typing this but like Ruby, Scala supports the Option approach to null handling i.e. you can return an Option[String]. From here use getOrElse and write
val result = employees.find(_=="Value Not Found).getOrElse("Nobody home")
Exception
Standard languages support try/catch/finally approoach.
try {
} catch(Exception e) {
} finally {
}
Scala can use pattern matching so examine the result.
val outcome = Try(10/0)
outcome match {
case Success(value) => println("Time for bed")
case Failure(er) => println("Computation failed, " + e.getMessage)
}
Either
Either works like Option except it is a tuple where you can place the result in one side and the error in the other.
def stringToInt(in: String): Either[String, Int] = {
try {
Right(in.toInt)
} catch {
case e: NumberFormatException => Left("Error: " + e.getMessage)
}
}
Pattern Matching
Introduction
Just like Ruby sigh!! Switch statements without breaks.
val number = 5
number match {
case 0 => "zero"
case 5 => "five"
case 9 => "nine"
case _ => "default" // Must be last
}
Matching On Attributes
We can have attributes as matches and ignore some content
case class Track(title: String, artist: String, trackLength: Int)
myTrack match {
case Track(_,_, 100) => "Long Track"
case Track(_,"Bowie", _) => "Bowie"
case _ => "No Track"
}
Matching On Sequence
We can match on sequences by using wildcards to denote we do not card about data beyond a certain value
val numbers = List(1,2,3,4,5,6,7)
numbers match {
case List(_, second, _*) => second
case _ => -1
}
Matching On Type
We can of course match on type too
myThing match {
case t1: ThingType1 => println(s"You have won a ${t.getReadableName()}")
case t2: ThingType2 => println(s"You have won a ${t2.getReadableName()}")
case a1: AnotherType => println(s"You have won a ${a1.getName()}")
case _ => "Nought"
}
Matching On With Filtering
We can add expression to our filters too provided they are a Boolean
myThing match {
case t1: ThingType1 if t1.getName().contains('Ice") => println(s"You have won a ${t.getReadableName()}")
...
case _ => "Nought"
}
Scala Collections
Introduction
All the collections have a mutable and immutable version where immutable is imported by default. Here is the class hierarchy.
- Seq
- Indexed can access by the index
- Linear only access by the head or tail
- Set
- Sorted You can have ordering and uniqueness
- BitSet A set of non-negative integers depicted as arrays.
- Map
- Sorted Map This is Unique Key and Sorted
List
- head gets first element
- tail gets the inverse of head e.g. [1,2,3,4] return [2,3,4]
- last gets the last element
- init gets the inverse of last e.g. [1,2,3,4] return [1,2,3]
- numbers :+5 adds to the end
- 0 +: numbers adds to the start
- numbers ++ List(5,6,7) combines at the end
- List(-1,0) ++ numbers combines at the beginning
- numbers.drop(2) will drop 2 from the left
- numbers.dropRight(3) will drop 3 from the right
- numbers.dropWhile(_ < 3) will drop if less than 3
Sets
These are similar to list except the duplicates are combined
Maps
Not many surprises
- weekDays(1) returns the value of key 1
- weekDays + (4 -> "Wednesday") adds a key/value
- weekDays -1 removes a adds a key/value from the left
- weekDays.foreach(entry => println(s"${entry._1} => ${entry._2}"))) iterates
Numerical Ops
For numbers we can call function which act on all members.
- min
- max
- sum
We can filter easily with predicate remembering we can drop the name in anonymous functions where used once gives a nice syntax to get the filtered values.
peadeLoadTimesInSeconds.filter(_ >= 10).min
Conversion
We can call functions on the collection to convert from one type to another. E.g. mySet.toList() will convert to a set to a list
Map operator (Transform)
Map is provide in scala and works like many map functions in other languages
Flatten and FlatMap
We can use flat map to flatten results
val nestedList = List(List(1,2,3), List(4,5,6,7))
// With flatten we can combine and add 1
nestedList,map(aList => aList.map(_ + 1)).flatten
// List(2,3,4,5,6,7,8)
// With flatMap we can do this more efficiently with
nestedList,flatMap(aList => aList.map(_ + 1))
// List(2,3,4,5,6,7,8)
Reduce, Fold and Scan
Reduce
Reduce applies an operator to the elements of the sequence. In the example below this would apply the operator + to each element giving the result 41.
val lst = List(1,2,3,5,7,10,13);
lst.reduceLeft(_+_) // or
lst.reduceLeft( (x,y) => {
println(x + " , "+ y);
x +y;
})
Fold
Fold allows you to pass the initial argument
val lst = List(1,2,3,5,7,10,13);
lst.foldLeft(100)(_+_) // 141
Scan
This is like Fold but will provide the result as a List
val lst = List(1,2,3,5,7,10,13);
lst.foldLeft(100)(_+_) // List(100, 101, 103, 106, 111, 118, 128, 141)
Right
It should be noted that the right operation do not use recursion and can as such run out of resources.
Concurrency
Concurrency and Parallelism
Just in case we have forgotten Concurrancy is when we do many things across a processor, timeslicing and Parallelism is when we do thing in parallel
Currying
Currying is the technique of transforming a function with multiple arguments into a function with just one argument. The single argument is the value of the first argument from the original function and the function returns another single argument function. This allows lazy evaluation of the code
Java Example
public static int add(int a, int b) {
return a + b;
}
into something like this (where Function<A, B> defines a single method B apply(A a)).
public static Function<Integer, Function<Integer, Integer>> add() {
return new Function<Integer, Function<Integer, Integer>>() {
@Override
public Function<Integer, Integer> apply(final Integer x) {
return new Function<Integer, Integer>() {
@Override
public Integer apply(Integer y) {
return x + y;
}
};
}
};
}
This evaluates to
add(); // gives back a instance of Function<[A, B]>
add().apply(1); // gives back a instance of Function<[A, B]>
add().apply(1).apply(1) // gives 2
Scala Example
In Scala, the regular uncurried function would look like this.
def add(x: Int, y: Int): Int = {
x + y
}
As Scala supports curried functions, you can turn this into it’s curried version simply by separating out the arguments.
// shorthand
def add(x: Int)(y: Int): Int = {
x + y
}
Which is shorthand for writing it out like this.
// longhand
def add(x: Int): (Int => Int) = {
(y: Int) => {
x + y
}
}
Futures
General
We can use futures which are a bit like Promises
var fut = Future {Thread.sleep(100000); 21 + 21}
fat.isCompleted // returns false
fut.onCompleted({
case: Success(result) => println("Got: " + result)
case: Failure(e) => println("Error: " + e)
})
Map and Filter
We can apply a map and filter operator to the future too
var salary = Future {Thread.sleep(100000); 40000}
var salaryWithBonus = salary.map(value => value + 500)
Collect
This allows us to apply rules on how to transform data.
val salaryIncremented = salaryFuture.collect {
case salary
if salary < 5000 => salary + 10000
}
VS Code
I used https://shunsvineyard.info/2020/11/20/setting-up-vs-code-for-scala-development-on-wsl/ for installing with VS Code.