This article was originally posted on my blog.
In the last few weeks, I started learning the basics about the Scala programming language. I wanted to learn a language that was popular and effective in concurrent programming and distributed applications. In this article, I will talk about the basics of Scala, some cool aspects and features of the language that separate it from the most common languages like Java and Python, and provide a set of amazing resources to learn Scala and go from beginner to expert.
What is Scala?
Scala is a general-purpose language that combines two programming paradigms: object-oriented programming and functional programming. Scala source-code can be compiled to Java bytecode, so it can run on the Java Virtual Machine (JVM), and is compatible with existing Java programs and libraries.
If you are familiar with more traditional OOP languages like Java, you will find most Scala code as not too difficult to read and understand. In Scala there are classes, methods, fields, and the overall syntax is not too different from Java. However, one difference between these two languages is that in Scala, everything is an object: even Java primitive types like int
and float
. Every variable is an object and every operator is a method.
As it was mentioned, Scala also combines the OOP paradigm with the advantages of functional programming. Functions are treated like "first-class citizens", as they are also variables (and therefore, objects). You can pass them as an argument to other functions or use them as a return value, for example.
Some other features of Scala that are worth mentioning are:
- Type inference. You can, but don't need to explicitly specify the data type of a variable or the return type of a function.
- Immutability. In Scala we usually operate with immutable values/objects. Any modification should just return another object. This is a major advantage for multithreaded or distributed environments, as it prevents a lot of race conditions.
-
Lazy Computation. Lazy computation means evaluating an expression or calculating something not when the expression is declared, but only when the result is needed in another computation. You can declare a lazy variable using the keyword
lazy
. -
Good set of collections. Scala provides various classes and traits to collect and manage data, like
List
,Seq
,Set
,Map
, etc. -
Concurrency support and control. Scala provides in its standard library the
Actor
class, which gives a higher level of abstraction for writing concurrent and distributed systems because we don't have to directly deal with explicit locking and thread management. There are also tools that support Scala in distributed and concurrent applications, like the Akka toolkit.
While Scala is a general-purpose language that can be used for a lot of things, and can basically be used for anything Java can be used for, its main application seems to be in concurrent programming and distributed applications.
A Simple Example
Here is a very simple example in Scala, where we use a list to calculate the sum of a set of numbers. We then create a new list by appending a new element to the already existing list, and calculating the new sum of elements.
object Example {
def main(args: Array[String]) = {
val l = List(1, 2, 3)
val lsum = l.sum
println(s"$l - Sum: $lsum")
val l2 = l :+ 4
val l2sum = l2.sum
println(s"$l2 - Sum: $l2sum")
}
}
Here is a short description of the code:
- We define a
main
function inside the Exampleobject
, where all the code is contained. - Declaring an
object
is like declaring a new class, and creating the singleton instance for it. - We then use the
val
keyword to declare an immutable variable (constant), and we create a newList
with the elements 1, 2 and 3. - We use
l.sum
to calculate the sum of all the list elements. - The
println()
function is then invoked to print to the console the contents of the file and the sum of all elements. We use string interpolation to insert in the string the value of the variables, using the$
character. - We use
:+
to append an element to the list. Notice thatl
is not modified, instead we use a new immutable variablel2
to store the result. - We then repeat the same operations for the new list
l2
.
Let's say this program was stored on a file called FirstExample.scala
. We first need to compile the contents of the file to Java bytecode, and then we can execute the generated bytecode.
To compile, we can use scalac
like so:
> scalac FirstExample.scala
And to execute the generated bytecode we can do:
> scala FirstExample
Here's what we can see on the console after we execute the program:
List(1, 2, 3) - Sum: 6
List(1, 2, 3, 4) - Sum: 10
Some Other Cool Things about Scala
(Almost) Everything is an expression
For the most part, almost everything in Scala can be classified as an expression, meaning that it evaluates to a certain value. One thing that can be used as an expression in Scala, in contrast to most common languages, is an if-statement. That allows us to do stuff like this:
val value = if (something > 42) 55
else if (something > 23) 45
else if (anotherSomething > 84) 1000
else 0
Another feature of Scala that also can be used as an expression is code blocks. Code blocks are groups of instructions or statements inside curly brackets; the value returned by the code block is the value generated by its last statement. We can then do stuff like this:
val aCodeBlock = {
val localValue = 67
localValue + 3 // value returned by the code block
}
The variable that is receiving the value from the code block would be assigned with the value 70.
map, flatMap, filter and for-comprehensions
The 3 methods map
, flatMap
and filter
are some of the most commonly used methods when dealing with data structures of any kind, and if you've done any significant programming in Scala, you likely already used them at some point. These methods allow us to do some powerful things in very little code, removing the need for many loops and iterations when using things like lists.
Let's start by creating a very simple List
:
val l = List(1, 2, 3)
We can use the map
method like so:
val l2 = l.map(_ + 1)
println(l2) // List(2, 3, 4)
This creates a new list, applying the function passed as an argument to map
to every element of the list.
We can use filter
to select only some elements from the list:
val l3 = l.filter(_ % 2 != 0)
println(l3) // List(1, 3)
This way, we create a new list l3
containing only the odd elements of the original list.
The flatMap
method is similar to the map
method, but flattens the result of various lists into just one. Let's say we have the following function in our code, that receives an integer, and outputs a list containing the original value and the square of that value:
val toSquare = (x: Int) => List(x, x*x)
We can then use flatMap
like so:
val l4 = l.flatMap(toSquare)
println(l4) // List(1, 1, 2, 4, 3, 9)
However, for more complex things, we might need to use a combination of these 3 methods, and the code might not be as readable as we want. Let's take another example, where we have two lists, one with numbers and the other with names, and we want to create strings representing the combination of all names with only the odd numbers.
val names = List("Bob", "Shirley")
val numbers = List(1, 2, 3)
With the methods shown above, we could do something like this:
val result = names.flatMap(name => numbers.filter(_ % 2 != 0).map(number => "" + name + number))
println(result) // List(Bob1, Bob3, Shirley1, Shirley3)
As you can see, it's not the most readable thing. Another way we can do it is by using for-comprehensions:
val result2 = for {
name <- names
number <- numbers if number % 2 != 0
} yield "" + name + number
println(result2) // List(Bob1, Bob3, Shirley1, Shirley3)
Much better!
Pattern Matching
This is something that is present in some other programming languages, like Haskell, for example.
If you are familiar with C++, Java, or other popular languages, switch statements are nothing new to you.
val value = 55
val order = value match {
case 1 => "first"
case 2 => "second"
case 3 => "third"
case _ => value + "th" // default case
}
However, in Scala we can do what is called pattern matching: instead of trying to match a variable against concrete values, we can match the variable against a certain structure, and then use its inner variables or values in the match expression.
Like this:
val tuple = ("Bob", 23) // creating a tuple
val result = tuple match {
case (name, age) => s"$name is $age years old"
case _ => "Default case"
}
println(result) // Bob is 23 years old
or this:
val myList = List(1, 2, 3)
val result = myList match {
case List(_, 2, _) => "This list contains 3 elements and has a 2 as the 2nd element"
case _ => "Idk what this is"
}
Some good resources to learn the basics of Scala
If you are interested in exploring what Scala has to offer, you can check out the following resources that I found useful for myself:
- Scala at Light Speed - Fast-paced Scala course on YouTube, that covers the absolute basics. A good place to start π
- Scala Online Resources - The official Scala website and documentation has a curated list of free (!) resources that allow you to master Scala and go from beginner to expert.
- ScalaFiddle - Lets you write Scala in your browser. If you want to explore and play with Scala before taking the time to install things locally, or if you just want to do quick experiments and tests, ScalaFiddle might be useful to you.
As with all programming languages, learning Scala is not just about watching content. Nothing replaces practice and actually writing code, so you can try to implement some basic algorithms in Scala, or solve some problems on Leetcode π
Conclusion
Scala is a language that I definitely want to learn more about in the future. Most importantly it's a door into a new paradigm that I never explored before, which is functional programming.
In the future, I will be doing more Scala posts, when I learn new stuff about the language, and hopefully also share some projects that use it π
As always, thank you for reading, and until next time!
Top comments (0)