DEV Community

Rocky Warren
Rocky Warren

Posted on • Originally published at rocky.dev on

Scala: The Good Parts

Scala allows you to accomplish tasks in different ways. I recently gave a talk explaining some of the more readable, maintainable approaches while attempting to add detail so the slides could stand on their own. The excellent reveal.js framework lets you create presentations in Markdown. This blog is also in Markdown, so through the power of copy/paste, here it is.


Scala Overview, Benefits Over Java

  • Created by Martin Odersky 17 years ago
  • Runs on the JVM
  • Can create Java objects, call their methods, and inherit from them in Scala and vice versa
  • Java collection interoperability
 import scala.jdk.CollectionConverters._ val javaList = java.util.Arrays.asList("Hi") val scalaList = javaList.asScala scalaList.foreach(println(_)) // Use any Scala collection methods val backToJava = scalaList.asJava 
Enter fullscreen mode Exit fullscreen mode
  • Type inference
 List<String> msgs = Arrays.asList("Hi"); // This Java code... 
Enter fullscreen mode Exit fullscreen mode
 val msgs = List("Hi") // ..becomes this Scala code 
Enter fullscreen mode Exit fullscreen mode
  • Concise
 // This Java code... public class Person { private String name; public String getName() { return name; } public String toString() { ... } public boolean equals() { ... } public int hashCode() { ... } } Map<String, Integer> map = new HashMap<String, Integer>() {{ put("a", 0); put("b", 2); }}; 
Enter fullscreen mode Exit fullscreen mode
 // ...becomes this Scala code case class Person(name: String) val map = Map("a" -> 0, "b" -> 1) 
Enter fullscreen mode Exit fullscreen mode
  • Concurrency
    • Immutability by default enables safe concurrency and parallelism
    • Parallel collections, concurrent distribution with actors, asynchrony with futures
  • Traits
    • Java interfaces with optional behavior, allows for safe multiple-inheritance
  • Pattern Matching
    • More powerful switch, match against class hierarchies, sequences, and more
  • Higher-order Functions
    • Use functions anywhere and pass them to anything with type safety
  • Option instead of null, no more NullPointerException!
  • Functional paradigms and excellent collection methods
    • map, flatMap, filter, head, headOption, isEmpty, foreach, contains, find, zip, zipWithIndex, reduce, fold, sum, count, groupBy, mkString, sortBy
  • Automatically returns last statement without explicit return, no semicolons
 def doubleIt(n: Int) = n * 2 // Returns `n * 2`, infers return Int type 
Enter fullscreen mode Exit fullscreen mode
  • if statements are expressions, can have results
 // Java's ternary (`int x = condition ? result : defaultVal`) in Scala val x = if (condition) result else defaultVal 
Enter fullscreen mode Exit fullscreen mode
  • Powerful imports
 // Rename import import java.util.{HashMap => JavaHashMap} // Ignore HashMap, import everything else import java.util.{HashMap => _, _} // Follows scoping rules def someFn() = { import cats.implicits.toShow ??? // Aside: This is valid Scala, throws NotImplementedError if hit } 
Enter fullscreen mode Exit fullscreen mode

Declaring variables

val items = Seq("1", "2") // Type inferred val typed: Seq[String] = Seq("1", "2") // Can optionally provide type var mutable = 5 // Int, prefer val over var var small: Short = 5 // Short, need type or inferred as Int val big = 5L // Long, need L or inferred as Int val bigger = 5.0F // Float, need F or inferred as Double val biggest = 5.0 // Double val javaType = new java.math.BigDecimal(5) // Java interop val byte: Byte = 0xa // Byte using hex notation, need type or inferred as Int val char = 'D' // Char val nothing = () // Unit, similar to void in Java lazy val deferred = ??? // Initialization deferred until first access 
Enter fullscreen mode Exit fullscreen mode

Classes

class Counter { private val myValue = 2 // No accessors generated val value = 1 // Only getter generated var mutableValue = 0 // Getter and setter generated, prefer val to var } class Person(val name: String) { // Primary constructor // Constructor body, initialize things here def this() { // Auxiliary constructor this("unnamed") // Must call primary constructor } } 
Enter fullscreen mode Exit fullscreen mode

Traits

  • Like Java interfaces, but can have implementation
  • Only difference between Scala class: cannot have constructor parameters
// Optionally sealed, barring extension outside file, useful for enums trait Logger { def log(msg: String): Unit // Abstract method def info(msg: String): Unit = println(msg) // Implementation provided } // Mix as many traits into class as you like, constructed left to right class Logged(name: String) extends Person(name) with Logger { // Must implement abstract methods override def log(msg: String): Unit = ??? def logName(): Unit = info(name) // Using implementation in trait } 
Enter fullscreen mode Exit fullscreen mode

Objects

  • Use for Singletons or home for misc values/functions
  • Can extend classes or traits, cannot have constructor parameters
  • Commonly used as "companion object" to classes for static functions
object Accounts { // Singleton private var lastNum = 0 def uniqueNum(): Int = { lastNum += 1; lastNum } } class Person private(val name: String) // Companion object object Person { // apply is special, called as `val p = Person("Rocky")` def apply(name: String): Person = new Person(name) // Called as Person.staticFn() as in Java def staticFn() = ??? } 
Enter fullscreen mode Exit fullscreen mode

Trait Initialization Order Gotcha

trait MyTrait { // scalafix error: abstract val in trait // Use def, lazy val, or move to object instead val foo: Int // Defaults to 0 val bar = foo * 2 // Initialized prior to foo getting set to 20 by MyObject } object MyObject extends MyTrait { val foo = 20 } MyObject.bar // 0 instead of 40 as expected! 
Enter fullscreen mode Exit fullscreen mode

Case Classes

  • Special kind of class, immutable with implementations for
    • toString, equals, hashCode
    • apply, making new unnecessary on creation
    • unapply for pattern matching
    • copy for immutable modifications
case class Person(name: String, age: Option[Int]) // No new required val r = Person("Rocky", None) // Calls apply println(r) // Person(Rocky,None) val b = r.copy(name = "Bob") // New object, r unchanged println(r == b) // false println(r == r.copy()) // true 
Enter fullscreen mode Exit fullscreen mode

Scala's class hierarchy


Option

  • A much better null
  • Calling get if value is None results in exception, no better than NullPointerException
  • Instead, handle both Some and None cases explicitly
val maybeName: Option[String] = Some("Rocky") maybeName match { // Good, but verbose case Some(n) => println(n.toUppercase) case None => println("N/a") } println(maybeName.fold("N/a")(_.toUpperCase)) // Better, but can be unclear println(maybeName.map(_.toUpperCase).getOrElse("N/a")) // Best for clarity // Wrap potentially null values in Option val maybeVal: Option[Int] = Option(javaMethodThatMayReturnNull()) // Conditionally set val with Option.when val maybeId: Option[UUID] = Option.when(req.hasId)(req.getId) 
Enter fullscreen mode Exit fullscreen mode

Try

try someFn() catch { // Standard try with pattern matching catch case ex: IOException => ??? case t: Throwable => ??? } // Try type can be chained val res: Try[Int] = Try(someFn()) .flatMap(d => Try(someFn()) // Without flatMap, res would be Try[Try[Int]] .map(d / _) ) res match { // Good, but verbose case Success(a) => println(a) case Failure(ex) => println(ex.getMessage) } res // Better, but can be unclear .fold(ex => println(ex.getMessage), println(_)) res // Best for clarity and to pattern match .map(println(_)) .recover { case ex: IOException => println(ex.getMessage) } // To only need to handle failed case res.failed.map(ex => println(ex.getMessage)) 
Enter fullscreen mode Exit fullscreen mode

Either

// Return either an error or Widget def getById(id: UUID): Future[Either[NotFoundError, Widget]] = database .run("...") .map(_.toRight(NotFoundError())) // Handling the Either at service boundary implicit class FromEither[T](future: Future[Either[NotFoundError, T]]) { def completeWith(obj: T) = future.onComplete { // With pattern matching case Success(Right(res)) => // Successful future with Widget res case Success(Left(error)) => // Successful future with Error throw fromServiceEx(error) case Failure(error) => // Failed future throw error } // Same as above using map/fold def completeWith(obj: T) = future .map(_.fold( error => throw fromServiceEx(error)), // Successful future with Error res => res) // Successful future with Widget ) .recover { case error => throw error } // Failed future } 
Enter fullscreen mode Exit fullscreen mode

Scala immutable collections hierarchy

  • Prefer immutable, they're safer and still performant
  • Map: key/value pairs, similar to Java's HashMap
 val myMap = Map("a" -> 0, "b" -> 1) myMap("b") // 1 // Looping over map, also shows string interpolation myMap.foreach{ case (k, v) => println(s"$k -> $v") } 
Enter fullscreen mode Exit fullscreen mode
  • Tuple: aggregates of values, useful for multiple returns in class's private methods. Prefer case class return types for public methods for readability.
 val myTuple = (1, 3.14, "Fred") myTuple._2 // 3.14 
Enter fullscreen mode Exit fullscreen mode
  • Seq: general purpose, similar to Java's List
 val mySeq = Seq(3, 2, 1) mySeq .filter(_ > 1) .sorted .zipWithIndex .foreach { case (v, idx) => print(s"$v at $idx ") } // 2 at 0 3 at 1 
Enter fullscreen mode Exit fullscreen mode
  • Set: distinct elements, similar to Java's HashSet
  • IndexedSeq: fast random access and count, similar to Java's Array
  • List: fast beginning and end operations, similar to Java's LinkedList

Pattern Matching

  • Preferable to casting via asInstanceOf, isInstanceOf
obj match { // Type matching case x: Int | Long if x < 10 => x // Multi-match with if guards case s: String => Integer.parseInt(s) // No cast needed, s is now String case _ => 0 // Default case, often required to avoid MatchError } Seq(0, 1, 2) match { // Collection matching case Seq(x, y) => s"$x $y" // List equivalent, case x :: y :: Nil case Seq(0, _*) => "0 ..." // List equivalent, case 0 :: tail case _ => "Not matched" } person match { // Case class/Option matching case Person("Rocky", age) => println(s"Rocky is $age") case Person(name, Some(_)) => println(name) case _ => println("Not matched") } 
Enter fullscreen mode Exit fullscreen mode

Additional Reading

Scala for the Impatient - Cay S. Horstmann


For more on learning Scala, check out my Scala Learning Resources.

Top comments (0)