This is a “cheat sheet” for the Scala 3 programming language. I initially wrote it for my book Learn Functional Programming The Fast Way, but I thought I’d share it here as well.
If you want to see many more details about Scala 3, see:
- The free, online Scala 3 Book (which I co-wrote)
- My book, Learn Scala 3 The Fast Way!
- The Scala Cookbook (2nd Edition)
- Functional Programming, Simplified
- My free 115-page e-book, Learning Recursion
Contents
- Key Concepts
- OOP Support
- FP Support
- Hello, world
- Variables
- Strings
- Most-Common Classes
- Language constructs
- Functions
- Functional error handling
- Domain Modeling (MD)
- Collections Classes
- Collections Methods
- Tuples
- Ranges
- A Little Scala 3 Application
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
Key Scala 3 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 object-oriented programming (OOP) and functional programming (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.
Scala 3 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 my books, such as the makeInt
function I often show:
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
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
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)