1. Overview
In Scala 3, significant indentation is a feature that’s enabled by default, streamlining our code for better readability. In this tutorial, we’ll see how this feature allows us to define code blocks using braces or relying solely on indentation.
2. Optional Braces
With significant indentation, we’re now able to leave some braces out. Furthermore, poorly implemented programs are flagged with warnings when using them or errors when leaving them out.
2.1. Embracing Significant Indentation: With and Without Braces
We can now write the same code with braces:
if (house == "Gryffindor") {
println("Ten points to Gryffindor!")
println("Well done, Harry!")
}
Or we could write it without them:
if (house == "Slytherin")
println("Ten points to Slytherin!")
println("Excellent, Draco!")
When using significant indentation, the compiler enforces a rule that helps us maintain well-structured code. Specifically, in a brace-delimited region, no statement can start to the left of the first statement right after the opening brace.
The compiler issues a warning on any code that breaks this rule:
if (house == "Hufflepuff") {
println("Ten points to Hufflepuff!")
println("Great job, Cedric!")
println("Oops!") // Compiler warning: indented too far to the left
}
-- Warning: indentation.scala:6:4
6 | println("Oops!") // Compiler warning: indented too far to the left
| ^
| Line is indented too far to the left, or a `}` is missing
1 warning found
These warnings are incredibly useful because they help us quickly identify and correct issues related to indentation, thereby reducing the likelihood of bugs and improving code readability.
2.2. Disabling Significant Indentation With -no-indent
Sometimes, we may disable significant indentation, such as when transitioning from Scala 2 or working on projects that follow a different coding style. The -no-indent compiler flag allows us to do just that.
By using -no-indent, we revert to the Scala 2 style, where braces are mandatory for defining code blocks:
if (character == "Luke Skywalker") { // This is now mandatory
println("May the Force be with you.")
println("You're a Jedi, Luke.")
} // This is also mandatory
When significant indentation is turned off, another rule comes into play. Some expressions, like if, allow us to have a code line in each branch, but the following line must have less indentation, or the compiler reports an error.
This means some code that used to work in Scala 2 will fail to compile in Scala 3.
For example, the following code works in Scala 2, printing “*After If!*” but fails with a compiler error in Scala 3:
val a = 0
if(a < 0)
println("Inside If")
println("After If!") // Compiler error in Scala 3: missing `{`
Disabling significant indentation can be helpful in specific scenarios, such as integrating Scala 3 code with a Scala 2 codebase.
However, it’s crucial to understand that turning off this feature can result in noisy builds due to the introduced warnings and errors. Therefore, we should consider the pros and cons before using the -no-indent flag.
2.3. Optional Braces for Class Definition
When defining a new class, trait, or object, we can omit braces using a colon (‘*:*‘). The following are now valid definitions in Scala 3:
trait A:
def f: Int
class C(x: Int) extends A:
def f = x
object O:
def f = 3
enum Color:
case Red, Green, Blue
new A:
def f = 3
package p:
def a = 1
package q:
def b = 2
2.4. Special Treatment of case Clauses
Scala 3 also introduced some changes to the syntax of match/case clauses. Prior to Scala 3, when using pattern matching, we’d need to use braces:
import scala.util.Random
val x: Int = Random.nextInt(10)
x match {
case 0 => "zero"
case 1 => "one"
case 2 => "two"
case _ => "other"
}
But in Scala 3, we can now rewrite without braces:
import scala.util.Random
val x: Int = Random.nextInt(10)
x match
case 0 => "zero"
case 1 => "one"
case 2 => "two"
case _ => "other"
2.5. The end Marker
One of the potential issues raised when discussing this new feature is that it can make the code more difficult to understand when a given block ends. To solve this problem, Scala 3 offers an optional end marker:
def largeMethod(...) =
...
if ... then ...
else
... // a large block
end if
... // more code
end largeMethod
The end marker consists of the keyword end together with a specifier token. The specifier token can be one of the following:
- if
- while
- for
- match
- try
- new
- this
- val
- given
-
3. Conclusion
In this article, we saw one of the many new features introduced in Scala 3, optional braces. This language feature allows replacing some usages of braces by following specific indentation rules.