Expressions:

2 + 2 // 4
println(1) // 1
println(1 + 1) // 2
println("Hello!") // Hello!
println("Hello," + " world!") // Hello, world!

Values

val x = 1 + 1
println(x) // 2

You can define any variable with the keyword val. Unlike the keyword var, val are immutable variables, meaning they cannot be changed during runtime. It is recommended to use val.

x = 3 // This does not compile

You can also assign the type of the value:

val x: Int = 1 + 1

Notice how the type declaration Int comes after the identifier x. You also need a :

Blocks

You can combine expression by surrounding them with {}. We call this a block.

println({
	val x = 1 + 1
	x + 1
}) // 3

Functions

You can define an anonymous function (i.e., a function that has no name) that returns a given integer plus one:

(x: Int) => x + 1

On the left of => is a list of parameters. On the right is an expression involving the parameters.

You can also name the functions:

val addOne = (x: int) => x + 1
println(addOne(1)) // 2

A function can have multiple parameters:

val add = (x: Int, y: Int) => x + y
pritnln(add(1, 2)) // 3

Or it can have no parameters at all:

val getTheAnswer = () => 42
println(getTheAnswer()) // 42

Methods

Methods look and behave very similar to functions, but there are a few key differences between them.

Methods are defined with the def keyword. def is followed by a name, parameter list(s), a return type, and a body:

def add(x: Int, y: Int): Int = x + y
println(add(1, 2)) // 3

Notice how the return type Int is declared after the parameter list and a :

A method can take multiple parameter lists:

def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier
println(addThenMultiply(1, 2)(3)) // 9

Or no parameter lists at all:

def name: String = System.getProperty("User.name")
println("Hello" + name + "!")

There are some differences, but for now, you can think of methods as something similar to functions. Methods can have multi-line expressions as well:

def getSquareString(input: Double): String = 
	val square = input * input
	square.toString
 
println(getSquareString(2.5)) // 6.25

The last expression in the body is the method’s return value. (Scala does have a return keyword, but it is rarely used.)

Classes

You can define classes with the class keyword, followed by its name and constructor parameters:

class Greeter(prefix: String, suffix: String):
	def greet(name: String): Unit = 
	println(prefix + name + suffix)

The return type of the method greet is Unit, which signifies that there is nothing meaningful to return. It is used similarly to void in Java and C. (A difference is that, because every Scala expression must have some value, there is actually a singleton value of type Unit, written, that carries no information.)

In Scala 2 you can make an instance of a class with the new keyword. In Scala 3, however, the new keyword is not needed thanks to universal apply methods:

val greeter = Greeter("Hello, ", "!")
greeter.greet("Scala developer!") // Hello, Scala developer!

We will cover classes in depth later.

Case Classes

Scala has a special type of class called a “case” class. By default, instances of case classes are immutable, and they are compared by value (unlike classes, whose instances are compared by references). This makes them additionally useful for pattern matching.

You can define case classes with the case class keywords:

case class Point(x: Int, y: Int)

You can instantiate case classes without the new keyword:

val point = Point(1, 2)
val anotherPoint = Point(3, 4)

Instances of case classes are compared by value, not by reference:

if point == anotherPoint then
	println(s"$point and $anotherPoint are the same.")
else
	println(s"$point and $anotherPoint are different.)
// ==> Point(1, 2) and Point(3, 4) are different.

There is a lot more to case classes that we would like to introduce, and we are convinced you will fall in love with them! We will cover them in depth later.

Objects

Objects are single instances of their own definitions. You can think of them as singletons of their own classes.
You can define objects with the object keyword:

object IdFactory:
	private var counter = 0
	def create(): Int = 
		counter += 1
		counter

You can access an object by referring to its name:

val newId: Int = IdFactory.create()
pritnln(newId) // 1
val newerId: Int = IdFactory.create()
println(newerId) // 2

We will cover objects in depth later.

Traits

Traits are abstract data types containing certain fields and methods. In Scala inheritance, a class can only extend one another class, but it can extend multiple traits.

You can define traits with the trait keyword:

trait Greeter:
	def greet(name: String): Unit

Traits can also have default implementation:

trait Greeter:
	def greet(name: String): Unit = 
	println("Hello, " + name + "!")

You can extend traits with the extends extends keyword and override an implementation with the override keyword:

class DefaultGreeter extends Greeter
 
class CustomizableGreeter(prefix: String, postfix: String) extends Greeter:
	override def greet(name: String): Unit =
	println(prefix + name + postfix)
 
val greeter = DefaultGreeter()
greeter.greet("Scala developer") // Hello, Scala developer
 
val customGreeter = CustomizableGreeter("How are you ", "?")
customGreeter.greet("Scala developer") // How are you, Scala developer?

Here, DefaultGreeter extends only one single trait, but it could extend multiple traits.
We will cover traits in depth later.

Program Entry Point

The main method is the entry point of a Scala program. The Java Virtual Machine requires a main method, named main, that takes one argument: an array of strings.

@main def hello() = println("Hello, Scala developer!")