Scala
I'll start with a disclaimer:
- These are notes written by an experienced Java dev, thus some level of basic programming knowledge is required
- I cannot possibly cover every unique feature of Scala, only what's most important to me
- Awesome and free tutorials are available on the Scala docs or RocktheJVM
Also I'll be focusing on Scala 2, rather than the most recent release of Scala 3. There are big differences in the syntax but the functionality remains mostly the same. My reasoning for this is the Government dataset I use is frozen on Scala 2.12.15. More tech-fluent companies may be using Scala 3, but those of us in healthcare and government should get comfortable with v2.
Overview of Types
Scala has types, similar to most other JVM based languages. However, one thing that is specific to Scala is starting variable declaration with val or var.
val someValue: Int = 42 // Immutatble
var someBool = false // mutable, type inferred
val is similar to final in Java, the variable is now a constant.constant, Whilewhile var can be reassigned to the same type. Type is usually optional as it can be inferred, but it's often recommended to use the type for ease of reading.
BesidesIt thisis feature,always thebest practice to use immutable objects. When modifying an object, it's best to return a new object:
val word = "Hello World"
val reversedWord = word.reverse
Other basic types are pretty much the same as in Java. You got your String, Int, Float, Boolean, etc. However, there are several ways to represent nothingness in Scala:
-
Nil - Used for representing empty lists, or collections of zero length. For sets you can use Set.empty
-
None - One of two subclasses of the Optional type, the other being Some. Very well supported by the Scala collections
-
Unit - Equivalent to Java's void which is used for functions that don't have a return type
-
Nothing - A trait. It is a subtype of all other types, but supertype of nothing. It is like the leaf of a tree. No instances of Nothing
-
Null - A trait. Not recommended to be used
-
null - An instance of the trait, similarly used as the Java Null. Not recommended to be used
The best practice when coding that could return null, is to use the Optional type.
Collections
A Scala Tuple is an immutable value that contains a fixed number of elements, which each has its own type.
val tuple1 = ("Test1", 25) // inferred type is (String, Int)
println(tuple1._1) // "Test1"
println(tuple2._2) // 25
Lists and Arrays must be all of the same type. A Scala List is an immutable data structure, ex. val sample: List[String] = List("type1", ...). While an Array is mutable, ex. val sample: Array[String] ] ["type1", ...]. Lists are usually preferred as they take less memory. The Java equivalent is LinkedList vs ArrayList.
Another way to construct a list is to use the "element1 :: element2 :: ... :: Nil" operator in Scala:
1 :: Nil // A list with only 1
Symbols
The biggest complaint I have about Scala is the rampant use of symbols without a proper appendix explaining what they do. I found this blogpost to be more informative than the actual docs in this area. I'll break down the most popular symbols:
- _ is widely used as the wildcard and pattern matching unknown symbols. It can also be thought of as the current or default value. We'll see more of this later
- + is for adding a single element
- ++ is for concatenating two collections
- : is for declaring associativity (type)
- :: represents prepending a single element or tuple of elements to a List
- ::: represents prepending a list with another list
Scala is very permissive with object names; You can define methods that start with ? or !. Methods that start with ! usually denote it communicates with actors Asynchronously.
Functional Programming
Scala is a functional programming language. While in most languages we think about the flow of instructions in sequence, but in a functional language we want to think in terms of composing the values through expressions. For example, observe the if expression below:
val ifExpression = if (testValue > 42) "String1" else "String2" // if-expression
This is much more readable than other ternary operators in other languages, this allows for cleaner chaining:
val ifExpression = if (testValue > 42) "String1"
else if (testValue == 0) "String2"
else "String3" // chained if-expression
Now at the core of functional programming is, of course, functions. In Scala a function can be defined as a single line or multiple line statement:
def myFunction(x: Int, y: String): String = s"$y is a string and $x is an int"
def myOtherFunction(x: Int): Unit = {
x + " is an int"
}
def factorial(n: Int): Int =
if (n<=1) 1
else n + factorial(n-1)
The above code demonstrates a few things at once:
- The return statement is not needed, the last line of the function is automatically returned
- Unit is the equivalent of void in Java, that is to say the return value does not have a meaningful value
- The string with the s"" represents a formatted string, where we can reference variables or expressions with $ or ${}
The key difference in functional programming, is DO NOT USE LOOPS OR ITERATION. We use recursion and functional mapping. If you're writing code with a lot of for loops, you're not thinking functionally.
Object-Oriented Concepts
Scala is a Object-Oriented language. It has objects, abstraction, and inheritance just like Java. For example:
object MyClass extends App {
class Animal {
val age: Int = 0
def eat() = println("Munch Munch")
}
val animal = new Animal
// inheritance
class Dog(name: String) extends Animal // constructor definition
val dog = new Dog("Fluffy")
// Doesn't work, constructor arguements are NOT fields
dog.name
class Cat(val name: String) extends Animal
val cat = new cat("kitty")
// this works, it was declared
cat.name
// subtype polymorphism
val someAnimal: Animal = new Dog("Spot")
someAnimal.eat()
abstract class WalkingAnimal {
def walk(): Unit // does not need to be initialized
}
// Interfaces are abstract types
trait Bird {
private val hasWings = true
def eat(animal: Animal): Unit
}
// single-class inheritance, multi-trait "mixing"
class PeacockDragon extends Animal with Bird {
override def eat(animal: Animal): Unit = println("I am eating animal!")
def ?!(thought: String): Unit = println("I just thought " + thought) // perfectly valid method name
}
val aPeacockaDragon = new PeacockDragon
aPeacock.aDragon.eat(aDog)
aPeacockaDragon eat aDog // This is infix notiation, only works for methods with 1 argument
aPeacockaDragon ?! "about pizza"
// singleton object, only one instance exists
object MySingleton {
val someVal = 1234
def someMethod(): Int = 5678
def apply(x: Int): Int = x + 1 // the apply method is special
}
// Both of the below are equivalent!
MySingleton.apply(54)
MySingleton(54) // the presence of an apply method allows the singleton to be invoked like a function
// Case classes are lightweight data structures with senisble equals, hash code, and serialization implementation
case class Person(name: String, age: Int)
// We don't need the new keyword because case class has a companion object with an apply method!
val bob = new Person("bob", 54) // equivalent to Person.apply("Bob", 54)
}
- An object is similar to a static type in Java, we don't have to declare a new instance to call it's functions
- All fields are by default public, there is no public keyword. But there is a private or protected keyword for methods/vars we wish to keep hidden
- A class is similar to that of Java, we need to create a new instance through its constructors to use it
- extends can be used to inherit functions from the parent class
- Note the extends App at the top level object makes the class runnable
- abstract classes and interfaces (traits) also exist, which do not need an implementation for every function
I'll briefly mention here that Scala can use Generics the same way that Java can. The T below could be any letter, and represents a type. I'm not going in depth on this as I find in practice these are seldomly used.
abstract class MyList[T] {
def head: T
def tail: MyList[T]
}
Exceptions
Exceptions work much the same as in Java, but the catch statement looks slightly different:
// code that could throw an error
try {
val x: String = null
x.length
} catch {
case e: Exception => "some fatal error message"
} finally {
// Execute some code no matter what
}