Skip to content

Instantly share code, notes, and snippets.

@nraychaudhuri
Last active August 29, 2015 13:57
Show Gist options
  • Select an option

  • Save nraychaudhuri/9494265 to your computer and use it in GitHub Desktop.

Select an option

Save nraychaudhuri/9494265 to your computer and use it in GitHub Desktop.

Getting Started with Scala

What is Scala?

Scala is a general-purpose programming language designed to express common programming patterns in a concise, elegant, and type-safe way. It smoothly integrates features of object-oriented and functional programming languages, enabling programmers to be more productive. Scala is an acronym for “Scalable Language”. This means that Scala grows with you.

Create a new Scala project using Activator

//TODO

Importing Scala project in Eclipse/IntelliJ

//TODO

Hello Scala

The most important feature of Scala programming language is it tends to be very enjoyable. No boilerplate, rapid iteration, but at the same time the safety of a strong static type system. Lets say hello to Scala. Lets start by creating a Scala WorkSheet inside Eclipse.

//TODO

  • scala worksheet intro
  • immutable
  • val/var
  • type inference
  • expressions

Lets look into some of the language features.

Classes

Scala is a pure object-oriented language. Conceptually, every value is an object and every operation is method call. The following defines a Scala class called Food with constructor that takes calories as parameter.

// Defining Classes with Constructor Params
class Food(val calories: Int)

Unlike Java, classes in Scala are defined with its primary constructor. The next line creates an instance of Food class by invoking the primary constructor.

//instantiating Food
val f = new Food(1000)

Scala treats val/var constructor parameters and fields as properties and will generate getter/setter methods.

//class with mutable field and method
class Recipe(val calories: Int) {
  var cookTime: Int = _ //sets 0, default value of Int

  //declares method
  def steps() = println("print all the steps...")
}

val r = new Recipe(100)
r.calories    //outputs 100
r.cookTime      //outputs 0
r.cookTime = 2  //sets the cookTime to 2
r.calories = 200 //compilation error calories is immutable

The method definition starts with the keyword "def". This is used to define all methods in scala. After def comes the name of the method, in this case it is "steps".

We can also create subclasses by extending abstract or non-final classes.

class Salad(val lettuceCalories: Int, val dressingCalories: Int) extends Food(lettuceCalories + dressingCalories)

When extending class we can also override members of parent classes. We can override val members with vals and def members with defs. We can also override defs with vals. Lets see an example.

// Overriding Methods with Values
// Scala implements the uniform access principle - methods and variables can both be accessed the same
class Menu(items: List[Food]) {
  def numberOfMenuItems() = items.size
}

// Dinners only consists of Salads
class Dinner(items: List[Salad]) extends Menu(items) {
  override val numberOfMenuItems = 2 * items.size //overriding def as val
}

val s1 = new Salad(5,5)
val s2 = new Salad(15,15)
val dinner = new Dinner(List(s1,s2))
println(s"Number Of Menu Items = ${dinner.numberOfMenuItems}") //prints 4

Case classes

Case classes in Scala are classes on steroid. When Scala compiler sees a case class, it automatically generates helpful boilerplate code so we don't have to.

case class Person(name: String, age: Int)

When we prefix a class with case, following things happens

  • They are immutable by default. Scala prefixes all parameters with val
  • Equals, hashCode and toString are implemented based on given parameters
  • Copy method is generate so we can easily create a modified copy of instance
  • A default implementation is provided for serialization
  • many more...

The Scala compiler also generates companion objects for case classes. The companion objects in Scala are singleton objects that share the same name as class name. The Person case class will translate to something like following:

class Person(val name: String, val age: Int) { ... }

object Person {
  def apply(name: String, age: Int) = new Person(name, age)
  ...
}

Here object Person is the companion object for class Person. Objects in Scala are singleton and can be used to implement factory design pattern.

val p = Person.apply("John", 26)

Scala provides syntactic sugar that allows objects with apply methods to be used as function calls.

val p = Person("John", 26) //same as Person.apply("John", 26)

We can also define additional apply methods inside classes and objects.

Traits

A trait is like an abstract class meant to be added to other classes as mixin. Traits can be used in all contexts where other abstract classes could appear. Trait can also be viewed as an interface with implemented methods and a class can extend multiple traits.

//declaring a trait with one abstract
trait Greetings {
  def sayHello: String
}

class JapanesseGreetings extends Greetings {
  override def sayHello: String = "konnichiwa"
}  

The JapanesseGreetings class extends the Greetings trait and implements the sayHello method. Lets make this example more interesting by adding one more trait.

trait DefaultGreetings {
  def defaultHello = "Hello"  
}

class GermanGreetings extends Greetings with DefaultGreetings {
  override def sayHello: String = "Guten Tag"
}

val g = new GermanGreetings
g.sayHello      //outputs Guten Tag
g.defaultHello  //outputs Hello

The GermanGreetings extends both Greetings and DefaultGreetings. The later is mixed-in to provide the default greetings behavior. Traits can also be mixed-in at the instance level.

val j = new JapanesseGreetings with DefaultGreetings
j.sayHello      //outputs konnichiwa
j.defaultHello  //outputs Hello

This particular instance of JapanesseGreetings will have both sayHello and defaultHello methods.

Lambdas & Closures

Lambdas are anonymous functions in Scala. The following creates an anonymous function that finds the successor of any given parameter

val succ = (i: Int) => { i + 1 } //creates a Int => Int function (takes a Int as a parameter and returns Int)

Here i is the name of the parameter and what comes after => is the body of the function. Functions are also values and are defined with val (methods are defined with def and they are not values).

We can invoke this function like we invoke methods

succ(10) //outputs 11

We can also pass function as a parameter to other functions and methods. In the following example succAndLog method takes two parameters, an integer value and a function.

def succAndLog(someInt: Int, succ: Int => Int) = {
   println(s"Incrementing $someInt")
   succ(someInt)
}

succAndLog(10, (i: Int) => i + 1) //outputs Incrementing 10 and returns 11

Closure is any function that closes over the environment in which it’s defined. Let’s use an example to explore this fact.

var percentage = 10
val applyPercentage = (amount: Int) => amount * percentage/100

In this case applyPercentage is a closure because it closes over the percentage variable defined outside the function.

applyPercentage(100) //outputs 10
percentage = 20      //modifying the percentage to 20
applyPercentage(100) //outputs 20

Lambdas and closures are different concepts, but they’re closely related. In the next section we will look into more usage of functions.

Collections

Scala collections library is one of the most powerful feature of the language. The library implements all the common data structures we need, making it essential for every Scala developer and its fun to use. Scala collections comes in many forms, it can be immutable, mutable and parallel. The language encourages you to use immutable collection API and imports them by default. Here is an example of some of the collections available in Scala

val numbers = Vector(11, 22, 33)  //creates sequence of numbers
val languages = Set("Scala", "Haskell", "Java") //set of strings
val nameAndGrades = Map("John" -> 'C, "Steve" -> 'A, "Mary" -> 'B) //map of key value pairs
val range = 0 to 10 //creates a range of all the numbers from 0 to 10

Now lets see how we can perform some common tasks using Scala collections.

//transform all the elements of the collection
val xs = Vector(10, 20, 30, 40)
val newList = xs.map(x => x/2) //map executes the given anonymous function for each element in the collection
println(newList) //outputs new sequence Vector(5, 10, 15, 20)
println(xs) //outputs the original collection because its immutable


//filtering
val xs = Vector(1, 2, 3, 4, 5).filter(x => x < 3)
println(xs) //outputs new collection Vector(1, 2)

//grouping
//groups elements in collections based on the return value of the given function
val groupByOddAndEven = Vector(1,2,3,4,5).groupBy(x => x % 2 == 0)
println(groupByOddAndEven) //outputs Map(false -> Vector(1, 3, 5), true -> Vector(2, 4))

//sorting
val lowestToHighest = Vector(3,1,2).sorted
println(lowestToHighest) //outputs Vector(1, 2, 3)

Scala provides mutable counterpart of all the immutable collections. For example following creates mutable sequence of numbers:

val numbers = scala.collection.mutable.ListBuffer(10, 20, 30)
numbers(0) = 40 //updates the zeroth element to 40
println(numbers) //outputs ListBuffer(40, 20, 30)

Scala provides another version of collection that evaluates elements in parallel. This is perfect for dividing work and take advantage of available processing power of multi-core processors. The following example creates an instance of parallel seq collection and transforms each element in parallel.

val numbers = scala.collection.parallel.ParSeq(1, 2, 3, 4, 5, 6, 7)
//map method of parallel collection tries to run the given function in parallel
//and evaluates each element in parallel
val newNumbers = numbers.map {x =>  
   println(s"Current thread ${Thread.currentThread.getName}") //prints the name of current thread
   x + 1
}

Run the above code few times to see how different threads are used for processing the map method.

Concurrency

Concurrency is hard unless you have right level of abstraction. Multi-threaded code with mutable state is very hard to maintain and reason about. And not to mention it is hard to debug. Scala takes a different approach, instead of threads Scala developers works with Future, Promise and Actors (see next section for references). This section will only explore Future.

A Future is an object holding a value, which may become available at some point. This value is usually the result of some other computation.

Once a Future object is completed a value or an exception, it becomes in effect immutable– it can never be overwritten. The simplest way to create a future object is to invoke the future method, which starts an asynchronous computation and returns a future holding the result of that computation. The result becomes available once the future completes.

import scala.concurrent._
import ExecutionContext.Implicits.global

def someTimeConsumingComputation(): Int = {...}
val f = future { someTimeConsumingComputation() }

The line import ExecutionContext.Implicits.global above makes the default global execution context available. Execution context execute tasks submitted to them, and you can think of execution context as thread pools. The future object uses the execution context to asynchronously execute the given task. In this case someTimeConsumingComputation.

Once the future is completed we can retrieve the value by registering callback. This callback will be invoked when the future is completed with value or exception:

future.onComplete {
  case Success(result) => println(result)
  case Failure(t) => println("An error has occured: " + t.getMessage)
}

The onComplete method takes a function that knows how to handle success and failure case. In this case the success case will have the result of the someTimeConsumingComputation method. A future fails when the task fails with an exception. For example following example will complete the future with exception:

val f = future { 2 / 0 }

Another way to get the value out of future is to block the current thread and wait the future is complete:

val f = future { someTimeConsumingComputation() }
//waits till the future is complete and prints the result
println(Await.result(f, scala.concurrent.duration.Duration.Inf))

The downside of this approach is it blocks the thread and affects the performance. Scala developers always prefer to use the callback approach.

Futures are very handy tool to run multiple parallel computations and then compose them together to come up with final result. The following example starts three asynchronous tasks and could run in parallel if we have CPU cores available:

def stockQuote(currency: String): BigDecimal = {...}

val usdQuote = future { stockQuote(USD) }
val chfQuote = future { stockQuote(CHF) }

Later we can compose these futures together to make a decision whether we should buy stocks in CHF:

 val isProfitable = for {
    usd <- usdQuote //returns the usd value when future is completed
    chf <- chfQuote //returns the chf value when future is completed
  } yield {
     //this will only be executed when both the futures complete
    isProfitable(usd, chf)
  }

We are using Scala's for-expression to compose two future objects. Inside the for {...} block we take the values from the future objects and check for profitable inside the yield {...} block

  isProfitable.onComplete {
    case Success(result) => println(result)
    case Failure(t) => println("An error has occured: " + t.getMessage)
  }

Finally registering callback to retrive the value of isProfitable method.

Further Learning

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment