A Scala 3 cheat sheet

This is a “cheat sheet” for the Scala 3 language. I initially wrote it for my book Learn Functional Programming Without Fear, but I thought I’d share it here as well.

If you want to see many more details about Scala 3, see:

Contents

  1. Key Concepts
  2. OOP Support
  3. FP Support
  4. Hello, world
  5. Variables
  6. Strings
  7. Most-Common Classes
  8. Language constructs
  9. Functions
  10. Functional error handling
  11. Domain Modeling (MD)
  12. Collections Classes
  13. Collections Methods
  14. Tuples
  15. Ranges
  16. A Little Scala 3 Application

Key Concepts

These are the main points or key concepts about the Scala 3 programming language:

  • It’s a high-level programming language (like Java, Kotlin, and others)
  • It’s expressive, with a concise, readable syntax (similar to Kotlin)
  • A distinguishing trait is that it promotes a fusion of OOP and FP
  • It’s statically-typed, but feels dynamic
  • It has a sophisticated type inference system, so you rarely need to explicitly declare variable types
  • Functions are first-class values, just like strings, integers, and other types
  • Immutable data structures are preferred, and mutable ones are also available
  • Scala 2 was very consistent, and Scala 3 is ridiculously consistent
  • It works seamlessly with other JVM libraries (Java, Kotlin, etc.)

More:

  • With the creation of Scala.js several years ago, Scala can be used as a JavaScript replacement
  • With the creation of Scala Native (and GraalVM), you can create binary executables
  • The new Scala CLI tool makes it easy to get started with Scala
  • The Scala 3 preference is to use significant indentation as the default, but you can also use curly braces, if you prefer

On a personal note, I expect that almost every popular programming language will support significant indentation within ten years. IMHO, it makes your code much cleaner and easier to read.

OOP Support

Scala is a complete OOP language, with full support for:

  • Classes
  • Traits (interfaces)
  • Inheritance
  • Polymorphism
  • Every value in Scala is an instance of a class

FP Support

Scala is a complete FP language, with full support for:

  • Functions as values
  • Higher-order functions
  • Lambdas
  • Everything is an expression that returns a value
  • Immutable (algebraic) variables
  • Immutable collections classes (data structures), with dozens of built-in functional methods

Hello, world

The most basic Scala 3 “Hello, world” example:

@main def hello = println("Hello, world")

The same thing, but with a variable:

@main def hello =
    val w = "world"
    println(s"Hello, $w")

If they’re in a file named Hello.scala, run them with Scala CLI like this:

scala-cli Hello.scala

Variables

Immutable (algebraic) variables are preferred, and are created with the val keyword:

// immutable variables (use as much as possible)
val a = 1
val b = 2.2
val c = "Hello"
val d = true

Create mutable variables only when necessary, with the var keyword:

// mutable variables (use rarely, if at all)
var a = 1
a = 2

You generally don’t need to explicitly declare data types, Scala can figure them out implictly:

// type inference
val a = 1          // Int
val b = 2.2        // Double
val c = "Hello"    // String
val d = true       // Boolean

Strings

Create multiline strings like this:

val s = """
    |Alvin Alexander
    |123 Main Street
    |Talkeetna, AK 99676
""".stripMargin

Embed variables inside strings like this:

val firstName = "Alvin"
val lastName = "Alexander"
val fullName = s"$firstName $lastName"

Most-Common Classes

Scala is an OOP and an FP language, so Int, Double, String, and Boolean are all implemented as classes.

Classes you’ll use all the time:

  • Int
  • Double
  • String
  • Boolean
val a = 10
val b = 2.2
val c = "Yo"
val d = true

Common sequences:

  • List
  • Seq
  • Vector
  • ArrayBuffer (mutable)

Maps:

  • Map

Other:

  • Tuple
  • Range
  • Set

See below for examples of all of those.

Language constructs

if/then/else

Here are a few if/then/else examples:

// every construct is an expression, so you can assign
// an if/then result to a variable:
val a = if x < 0 then -x else x

// you can also use if/then as the body of a function:
def compare(a: Int, b: Int): Int =
    if a < b then
        -1
    else if a == b then
        0
    else
        1
    end if   // this is optional

for loops

for loops are used for side effects, such as printing:

// single line:
for i <- ints do println(i)

// multiline:
for
    i <- ints
    if i > 2
do
    println(i)

// optionally close the expression with 'end for':
for
    i <- ints
    if i > 2
do
    println(i)
end for

for expressions (for/yield)

for expressions are true expressions that yield values:

// create a sample list:
val ints = List.range(1, 10)

// write for/yield like this:
val tenX = for i <- ints yield i * 10

// or this:
val tenX = for
    i <- ints
    if i > 5
yield
    i * 10

// or this:
val tenX =
    for
        i <- ints
        if i > 5
    yield
        i * 10

match expression

Scala is famous for being a pattern-matching language. As its name implies, match is an expression that yields a value:

val month = i match
    case 1  => "January"
    case 2  => "February"
    // more months here ...
    case 11 => "November"
    case 12 => "December"
    case _  => "Invalid month"  // the default, catch-all

// used for a side effect:
i match
    case 1 | 3 | 5 | 7 | 9  => println("odd")
    case 2 | 4 | 6 | 8 | 10 => println("even")

// a function written with 'match':
def isTrueInPerl(a: Matchable): Boolean = a match
    case false | 0 | "" => false
    case _ => true

Note: There are many more ways to use match expressions.

try/catch/finally

Scala’s try/catch/finally expressions are similar to Java, but are also expressions that return a value:

def readTextFile(filename: String): Option[List[String]] =
    try
        Some(Source.fromFile(filename).getLines.toList)
    catch
        case e: Exception => None
    finally
        // close any resources here

Functions

Scala functions and methods are created with the def keyword. They can be written on one line, or as many lines as needed:

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

def max(a: Int, b: Int): Int =
    // as many lines as you need here
    if a > b then a else b

Default parameter values

Function parameters can have default values:

def getUrlData(
    url: String,
    connectionTimeout: Int = 5_000,
    readTimeout: Int = 5_000
): String =
    println(s"cTimeout = $connectionTimeout, rTimeout = $readTimeout")
    // your real code would be here
    "Here’s the data!"

getUrlData("https...")                   // cTimeout = 5000, rTimeout = 5000
getUrlData("https...", 2_500)            // cTimeout = 2500, rTimeout = 5000
getUrlData("https...", 10_000, 10_000)   // cTimeout = 10000, rTimeout = 10000

A function that takes a function parameter

A three-step process.

[1] Define function that takes other functions as input parameters:

// callback is a "function input parameter" (FIP):
def sayHello(callback: () => Unit): Unit =
    callback()

// this defines a FIP that (a) takes no input parameters and
// (b) returns nothing (Unit is like void):
def sayHello(callback: () => Unit): Unit =
                       ----------

[2] Define other functions:

def helloAl(): Unit =
    println("Hello, Al")

def holaLorenzo(): Unit =
    println("Hola, Lorenzo")

[3] Pass those to sayHello:

sayHello(helloAl)        // prints "Hello, Al"
sayHello(holaLorenzo)    // prints "Hola, Lorenzo"

(See Functional Programming, Simplified for many more details on how this works.)

Functional error handling

Scala has the concepts of functional error handling and error-handling data types.

  • Functions should not throw exceptions
  • They should return functional error-handling types:
    • Option
    • Try
    • Either

This is demonstrated in many places in the book, such as the makeInt function:

def makeInt(s: String): Option[Int] =
    try
        Some(s.toInt)
    catch
        case e: NumberFormatException => None

Domain Modeling (MD)

When programming in an OOP style, you’ll primarily use these Scala constructs:

  • Traits
  • Enums
  • Classes
  • Objects

Class examples:

class Person

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

class Person(val name: String, val age: Int)
    override def toString = s"$name is $age years old"

When programming in an FP style, you’ll primarily use these constructs:

  • Traits
  • Enums
  • Case classes
  • Objects

DM: Classes

An OOP-style class has parameters that are created with the var keyword, so they can be changed (mutated) later:

// one-liner:
class Person(var firstName: String, var lastName: String)

// when it gets longer:
class Person(
    var firstName: String,
    var lastName: String
)

Create a new instance, then access and mutate its parameters/fields:

val p = Person("Reginald", "Dwight")

// access the fields:
println(p.firstName)   // prints "Reginald"
println(p.lastName)    // prints "Dwight"

// mutate them:
p.firstName = "Elton"
p.lastName = "John"

// print the new values:
println(p.firstName)   // prints "Elton"
println(p.lastName)    // prints "John"

A class with some enums as well:

enum CrustSize:
    case Small, Medium, Large

enum CrustType:
    case Thin, Thick, Regular

enum Topping:
    case Cheese, Pepperoni, Olives

class Pizza (
   var crustSize: CrustSize,
   var crustType: CrustType,
   val toppings: ArrayBuffer[Topping]
):

    override def toString = s"$crustSize $crustType with $toppings"

    // create a method to determine the pizza cost based on its attributes:
    def cost: Double = ???

    // create a method to determine the pizza price based on its attributes:
    def price: Double = ???

end Pizza

In that example, we use ??? as the method body until we’re ready to implement it.

DM: Enums

Scala 3 has enums, or enumerations:

enum Suit:
    case Clubs, Diamonds, Hearts, Spades

// some time later ...
import Suit.*

def printEnum(suit: Suit) = suit match
    case Clubs    => println("clubs")
    case Diamonds => println("diamonds")
    case Hearts   => println("hearts")
    case Spades   => println("spades")

DM: Traits

Traits can used like Java interfaces, and can also be used like Ruby mixins.

trait Animal:
    def speak(): Unit

trait HasTail:
    def wagTail(): Unit

class Dog extends Animal, HasTail:
    def speak(): Unit = println("Woof")
    def wagTail(): Unit = println("⎞⎜⎛  ⎞⎜⎛")

val d = Dog()

DM: Objects

Objects are used to create singletons. Functions in objects are like static methods in Java.

object StringUtils:

    def truncate(s: String, length: Int): String =
        s.take(length)

    def lettersAndNumbersOnly_?(s: String): Boolean =
        s.matches("[a-zA-Z0-9]+")
   
    def lettersOnly_?(s: String): Boolean =
        s.matches("[a-zA-Z]+")
   
    def containsWhitespace(s: String): Boolean =
        s.matches(".*\\s.*")

end StringUtils

DM: Case classes

Case classes start with the case keyword, and are intended for FP:

case class Person(name: String, relation: String)

val christina = Person("Christina", "niece")
christina.name            // "Christina"
christina.name = "Fred"   // error: reassignment to val

val aleka = christina.copy(name = "Aleka")
aleka.name                // "Aleka"

christina == aleka        // false

DM: Union types

Scala 3 has union types, which let your code be even more dynamic. This function takes an Int or a String as an input parameter:

// Perl version of "true"
def isTrueInPerl(a: Int | String): Boolean = a match
    case 0 | "0" | "" => false
    case _ => true

This one returns a union type as a function result:

def aFunction(): Int | String =
    val x = scala.util.Random.nextInt(100)
    if (x < 50) then x else s"string: $x"

For more information, see my blog post.

Collections Classes

Some common collections classes:

val xs = List(1, 2, 3)           // a simple linked-list
val xs = Vector(1, 2, 3)         // a high-performance, immutable sequence
val xs = ArrayBuffer(1, 2, 3)    // a high-performance, mutable sequence

val states = Map(
    "AK" -> "Alaska",
    "AL" -> "Alabama"
)

val s = Set(1, 2, 1, 3)    // s: Set(1, 2, 3)  (duplicates are dropped)

Collections Methods

Scala sequence classes have many methods with functional methods that take anonymous functions (lambdas) to solve problems. Here’s the List class and some of its methods:

val a = List(10, 20, 30, 40, 10)      // List(10, 20, 30, 40, 10)
a.distinct                            // List(10, 20, 30, 40)
a.drop(2)                             // List(30, 40, 10)
a.dropRight(2)                        // List(10, 20, 30)
a.dropWhile(_ < 25)                   // List(30, 40, 10)
a.filter(_ < 25)                      // List(10, 20, 10)
a.filter(_ > 100)                     // List()
a.find(_ > 20)                        // Some(30)
a.slice(2,4)                          // List(30, 40)
a.take(3)                             // List(10, 20, 30)
a.takeRight(2)                        // List(40, 10)
a.takeWhile(_ < 30)                   // List(10, 20)

And some more:

val evens = List(2, 4, 6)                  // List(2, 4, 6)
val odds = List(1, 3, 5)                   // List(1, 3, 5)
val fbb = "foo bar baz"                    // String = foo bar baz
val firstTen = (1 to 10).toList            // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val empty = List[Int]()                    // List[Int] = List()
val letters = ('a' to 'f').toList          // List(a, b, c, d, e, f)

evens.contains(2)                          // true
firstTen.containsSlice(List(3,4,5))        // true
firstTen.count(_ % 2 == 0)                 // 5
firstTen.endsWith(List(9,10))              // true
firstTen.exists(_ > 10)                    // false
firstTen.forall(_ < 20)                    // true
letters.indexOf('b')                       // 1 (zero-based)
letters.indexOfSlice(List('c','d'))        // 2
firstTen.indexWhere(_ == 3)                // 2
letters.isDefinedAt(1)                     // true
letters.isDefinedAt(20)                    // false
letters.isEmpty                            // false
empty.isEmpty                              // true

val fbb = "foo bar baz"
fbb.indexOf('a')                           // 5
fbb.lastIndexOf('a')                       // 9
fbb.lastIndexOfSlice("ar")                 // 5
fbb.lastIndexOfSlice(List('a','r'))        // 5
fbb.lastIndexWhere(_ == 'a')               // 9
fbb.lastIndexWhere(_ == 'a', 4)            // -1
fbb.lastIndexWhere(_ == 'a', 5)            // 5

firstTen.max                               // 10
letters.max                                // f
firstTen.min                               // 1
letters.min                                // a
letters.nonEmpty                           // true
empty.nonEmpty                             // false
firstTen.product                           // 3628800
letters.size                               // 6

firstTen.startsWith(List(1,2))             // true
firstTen.startsWith(List(1,2), 0)          // true
firstTen.startsWith(List(1,2), 1)          // false
firstTen.sum                               // 55

// fold and reduce examples
firstTen.fold(100)(_ + _)                  // 155
firstTen.foldLeft(100)(_ + _)              // 155
firstTen.foldRight(100)(_ + _)             // 155
firstTen.reduce(_ + _)                     // 55
firstTen.reduceLeft(_ + _)                 // 55
firstTen.reduceRight(_ + _)                // 55

firstTen.fold(100)(_ - _)                  // 45
firstTen.foldLeft(100)(_ - _)              // 45
firstTen.foldRight(100)(_ - _)             // 95
firstTen.reduce(_ - _)                     // -53
firstTen.reduceLeft(_ - _)                 // -53
firstTen.reduceRight(_ - _)                // -5

Tuples

A tuple is a heterogeneous collection of elements, which means that a tuple can contain different types of elements:

val t = (1, "yo")    // (Int, String)

A tuple that contains an Int, String, Char, and Double:

val t = (1, "1", '1', 1.1)

Examples:

val t = (42, "fish")

t(0)     // 42
t(1)     // "fish"

t.size   // 2

Ranges

Ranges let you create ranges of integers or characters:

1 to 5         // will contain Range(1, 2, 3, 4, 5)
1 to 10 by 2   // will contain Range(1, 3, 5, 7, 9)
1 to 10 by 3   // will contain Range(1, 4, 7, 10)

for i <- 1 to 10 do println(i)

A Little Scala 3 Application

This is a complete little Scala 3 application with a main method. You can run it with scala-cli to make an HTTP GET request:

//> using scala "3"
//> using lib "com.softwaremill.sttp.client3::core::3.7.2"
// the two previous lines are the Scala CLI way to say that
// you want to use (a) the latest version of Scala 3, and
// (b) the 'sttp' client library.

// run me with 'scala-cli HttpGet.scala'

import sttp.client3.*

@main def doGet =
    val backend = HttpURLConnectionBackend()
    val response = basicRequest
                      .get(uri"http://httpbin.org/get")
                      .send(backend)
    println(response)