Scala at a Glance — A Language a Day, Advent Calendar 2019 Day 7/24

About this ‘A Language a Day’ Advent Calendar 2019

Welcome to Day 7 of this year’s A Language a Day Advent Calendar. Today’s topic is introduction to the Scala programming language.

Facts about the language

Some facts about Scala:

  • Based on Java (both syntactically and using JVM)
  • Supports object-oriented and functional styles
  • Statically typed
  • Appeared in 2004
  • Website: www.scala-lang.org

Installing and running Scala

To run programs in Scala, you need first to have Java installed. Then, you can install either IntelliJ or a command-line set, sbt. Here’s the command line for the Mac users:

$ brew install sbt

On the main website, you can find a binary for your operating system.

To run a program, compile it to the JVM bytecode and then run it:

$ scalac hello-world.scala
$ scala HelloWorld

For the rest of this article, you can simplify the process and execute the code with a single command:

$ scala hello-world.scala

Hello, World!

Surprisingly, it is wise to take a look at three versions of the Hello, World! program. This will immediately give you some additional view of Scala’s relation to Java.

The simplest program is a one-liner:

println("Hello, World!")

This resembles a program in a scripting language. You can use the Java approach instead:

object HelloWorld {
    def main(args: Array[String]) = {
        println("Hello, World!")
    }
}

Here, the main function is defined in the Hello object (not class).

Finally, the Scala-way variant:

object HelloWorld extends App {
    println("Hello, World!")
}

The object keyword makes a single instance of such object, but this time it extends App. App is a pre-defined trait that has its own main method, so the program runs the code to print our message when the object is instantiated.

Variables

To create a variable, you use one of the two keywords: var or val. The var keyword creates a mutable variable, the val — immutable. The val variables in Scala are equivalent to the final variables in Java and they cannot be re-assigned.

In the following program, the greeting is made up from three parts, one of them is a variable containing the name:

val name = "John"
println("Hello, " + name + "!")

In this case, the type of the variable is known at compile time, but you can specify it explicitly.

val name: String = "John"
println("Hello, " + name + "!")

This is an error to leave a new variable undefined. The following code will not compile:

var name: String // error: '=' expected
name = "John"

Variables can be interpolated in strings:

val name = "John"
var age = 32
println(s"$name is $age years old.")

It is possible to interpolate a simple expression as well:

println(s"Next year he'll be ${age + 1}.")

Functions

Functions are declared with the def keyword. You have to specify the type of its parameters and optionally the result type. You supply the body of the function after the = sign. In the simple cases, no curly braces are needed:

def add(a: Int, b: Int): Int = a + b

println(add(42, 43))

The same function can be re-written with a traditional code block (but still with =):

def add(a: Int, b: Int): Int = {
    a + b
    // or: return a + b
}

A Factorial example

Let us implement computation of a factorial.

def factorial(n: Int): Int = 
    if (n < 2) 1 else n * factorial(n - 1)

println(factorial(1)) // 1
println(factorial(4)) // 24
println(factorial(5)) // 120

Here, the whole body is a one-liner with the ternary if. Notice that the factorial function does not change any variables, and is thus an implementation in the spirit of functional programming ideology.

An alternative way to implement it is to use a range.

def factorial(n: Int): Int = (1 to n).product

Later on this page, there will be an example of lambda functions (see the Sleep sort section).

Classes

Classes are defined with the class keyword.

In the simplest cases, when you need a class with data fields only, you only need to declare a constructor with the var (or val) parameters, which you can directly use as object properties. Objects are instantiated with the new keyword.

class Person(val name: String, var age: Int)

val p = new Person("Karla", 20)
println(p.name) // Karla
p.age += 1
println(p.age) // 21

Class methods

Class methods are functions defined in the class body. They have direct access to the fields of the instances. You do not need to pass or to use any form of the this pointer.

class Person(var name: String, val age: Int) {
    def info(): String = s"${name} is ${age} years old."
}

var p = new Person("Mike", 25)
println(p.info())

Singleton objects

In Scala, you can’t have static class methods, but you can instead create a singleton object. All you need to do is to use the object keyword in place of object. Actually, you don’t have to instantiate it ever more.

object Counter {
    var count: Int = 0
    def get_count(): Int = {
        count += 1
        count
    }
}

println(Counter.get_count) // 1
println(Counter.get_count) // 2
println(Counter.get_count) // 3

In this example, the count variable is a single field belonging to a single instance of the Counter object.

A polymorphic example

Scala allows us to use traits and abstract classes. Let us create an array of polymorphic objects and loop over them to see if the correct overloaded methods were called.

trait Animal {
    def info(): String
}

class Dog extends Animal {
    def info() = "Dog"
}

class Cat extends Animal {
    def info() = "Cat"
}

var zoo = Array(new Cat, new Dog, new Cat, new Dog)
for (x <- zoo) println(x.info)

Instead of a trait, we could use an abstract class:

abstract class Animal {
    def info(): String
}

The type restriction in the trait (or the abstract class) forces all info methods to return a string.

Concurrency

There are a few means that you can choose for concurrent programming. Among them, are threads and futures.

Sleep sort

Let us use futures to implement the sleep sort algorithm. The below implementation is intendedly written in the functional style.

import scala.concurrent.{Future, Await}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration

val data = Array(10, 4, 2, 6, 2, 7, 1, 3)

data.map(
    (n) => {
        Future {
            Thread.sleep(n * 100)
            println(n)
        }
    }
).map(
    (f) => Await.result(f, Duration.Inf)
)

Futures require some additional imports and the execution context. Note how you can use curly braces to import two modules whose paths differ partially only.

For each data element, the map method creates an instance of a Future that sleeps the given number of milliseconds and prints the number. Inside map, we have an anonymous function (n) => {...}, or (n: Int) => {...} alternatively.

On top of the first mapping, another one is called. It receives the futures (one per data item) and calls another lambda to make sure the program waits for the completion of the future.

If you do not like this approach, you can create a separate function taking a single argument, and pass its name to map. Here is the version of the part of the above program that does not use lambda functions embedded to the map calls. Notice that you cannot omit type annotation of function parameters.

def f1(n: Int) = Future {
    Thread.sleep(n * 100)
    println(n)
}

def f2(f: Future[Unit]) = Await.result(f, Duration.Inf)

data.map(f1).map(f2)

Get more

If you’d like to continue learning Scala, you have to learn a lot more things that were just covered. In particular, understanding the object hierarchy is required. For example, the difference between traits and abstract classes, multiple inheritance tricks, companion objects, etc. A separate set of features belongs to functional programming, such as filters, closures, tail recursion, etc.

You can continue with the official documentation, where you can find the full reference, a tour, the Scala book, and a lot more.

The code for today’s article you can find on GitHub.

Next: Day 8. Dart

Leave a Reply

Your email address will not be published. Required fields are marked *