1. Overview

Scala’s flexible syntax and expressive features make it an ideal choice for creating Domain-Specific Languages (DSLs). That flexibility allows us to write commands that look like native control expressions.

Scala uses symbolic method names, infix, and postfix notations, allowing us to write highly expressive code. Scala’s syntax and features, emphasizing expressiveness and conciseness, make it an ideal tool for hosting internal DSLs in many industries. For instance, financial institutions often leverage them to create DSLs that closely mirror their domains.

This tutorial will review the rules governing when we can use braces instead of parentheses and when we can leave parentheses out altogether.

2. Understanding Scala’s Syntax: Parentheses vs. Braces

The syntax rules governing using parentheses and braces are similar to those in Java, with a few notable exceptions.

Like in Java, we use parentheses to declare parameters in method definitions and calls and to make the expression evaluation order explicit. In Scala, we also use them for tuple literals and the Unit value, which could be understood as a tuple with zero elements.

On the other hand, we use braces to define code blocks, which are sequences of expressions or declarations executed together. A code block defines a scope for any value bindings declared on it. Blocks form the body of classes, methods, branches, flow control statements, and loop constructs.

In contrast to Java, Scala is expression-oriented, and code blocks are expressions. They return the value of the last expression in the block. We can use braces to write a block expression anywhere an expression is expected.

Like Java, we can omit the braces for single-line blocks in if-statements and loops and when declaring functions with a single-line body.

Let’s look at some examples of using parentheses and blocks in Scala:

object ScalaBlockExamples extends App {
  // A simple block returning a value
  val a = {
    val x = 5
    val y = 10
    // The last expression is the value of the block
    x + y
  }
  println(s"Value of a: $a") // Output: Value of a: 15

  // Block in an if-else construct
  val number = 7
  val result = if (number > 5) {
    // This block is executed because the condition is true
    "Greater than 5"
  } else {
    "Not greater than 5"
  }
  println(s"Result: $result") // Output: Result: Greater than 5

  // Block in a function definition
  def square(x: Int) = {
    // A block defining the body of the function
    x * x
  }
  println(s"Square of 4: ${square(4)}") // Output: Square of 4: 16

  // Block as an argument to a higher-order function
  val numbers = List(1, 2, 3, 4, 5)
  val doubledNumbers = numbers.map { n =>
    // A block that is passed as a lambda to the map function
    n * 2
  }
  println(
    s"Doubled numbers: $doubledNumbers"
  ) // Output: Doubled numbers: List(2, 4, 6, 8, 10)

  // Block in a for-comprehension
  val squares = for {
    n <- numbers
    if n % 2 == 0 // Filtering even numbers
  } yield {
    // A block that computes the square of each number
    n * n
  }

  println(s"Squares of even numbers: $squares")
  // Output: Squares of even numbers: List(4, 16)
}

3. Where and When to Use Braces or Parentheses

3.1. Whether to Use or Omit Parentheses

In Scala, the ability to omit parentheses in method calls that have only one argument is a notable feature that contributes to the language’s expressive and concise syntax. This is possible only when a method is invoked with exactly one argument and the method name is in an infix position.

This syntactical flexibility is particularly evident in methods designed to read like natural language or in DSLs.

Let’s consider a simple example to illustrate this concept. Suppose we have a method greet() that takes a single String argument:

object NoParentheses extends App {
  def greet(name: String): Unit = {
    println(s"Hello, $name!")
  }

  // This won't work
  // greet "Alice"
  
  // But this works and is equivalent to greet("Alice")
  this greet "Alice" 

}

In this example, the commented code in the seventh line won’t compile because the method call is not in an infix position. But we can leave the parentheses out if we explicitly reference the object on which we call the method greet(). For example, this greet “Alice” is syntactically valid and functionally equivalent to this.greet(“Alice”).

This feature is handy for enhancing the readability of code, especially when methods are named in a way that forms a readable phrase or sentence with their arguments.

3.2. Expressions, Expressions, Expressions

The essence of being expression-oriented is that almost everything is an expression and yields a value. This principle extends to blocks, sequences of expressions enclosed in braces {}. The value of the last expression in the block is taken as the value of the entire block. This allows for using block expressions as arguments in method calls, as long as the type of the value returned by the block matches the expected type of the method’s parameter.

For example, consider a method that takes an Int:

def square(number: Int): Int = number * number

val result = square({
  val x = 2 + 3
  x * x // This evaluates to 25
})

We can pass a block expression that evaluates to an Int. In this case, the block { val x = 2 + 3; x * x } is evaluated, and its result (25) is passed to the square() method.

In Scala, functions are first-class citizens, and we can return a function like any other value from any construct that returns values, including blocks.

3.3. Functions as Parameters

In Scala, functions can take other functions as parameters, a feature central to the language’s support for functional programming. When a function is designed to accept another function as a parameter, Scala’s syntax flexibility allows for passing a block of code as the argument, provided that this block represents a function with a signature compatible with the parameter’s expected type.

This capability is handy for creating more readable and expressive code, especially when dealing with higher-order functions that operate on other functions, either by taking them as inputs or returning them as outputs.

Let’s look at an example to illustrate this concept.

Suppose we have a higher-order function applyOperation() that takes a function as a parameter:

def applyOperation(a: Int, b: Int, operation: (Int, Int) => Int): Int = {
  operation(a, b)
}

val sum = applyOperation(5, 3, (x, y) => x + y)

This function expects a third parameter, operation, which is a function that takes two Int values and returns an Int.

Typically, we might pass a lambda expression to applyOperation(). However, we can also use a block to provide the function:

val sum = applyOperation(5, 3, { 
  (x: Int, y: Int) => 
  // This block represents a function that takes two Ints 
  // and returns their sum. 
  x + y 
})

In this case, the block { (x: Int, y: Int) => x + y } is a function that takes two Int parameters and returns their sum. This block is passed as the operation parameter to the applyOperation() function.

Using blocks to define function parameters keeps the code concise and enhances its readability, particularly for complex functions requiring more extended logic.

3.4. Bringing It All Together

One of the more powerful features of Scala is the ability to combine features liberally, and we can use higher-order functions, block expressions, and drop parentheses when calling one-parameter methods to write code that looks like built-in control structures.

Here’s the scenario: We want a method that measures the time a block of code executes. This method, measureTime(), will take a code block as a parameter and return the execution time along with the result of the code block. We leave out the parentheses, delimiting the code block only with braces, making the syntax cleaner and more expressive:

object TimingExample extends App {
  
  def measureTime[T](block: => T): (T, Long) = {
    val startTime = System.nanoTime()
    val result = block
    val endTime = System.nanoTime()
    (result, endTime - startTime)
  }

  // Using the measureTime method with braces
  val (result, time) = measureTime {
    // Code block whose execution time is to be measured
    val numbers = (1 to 1000000).toList
    numbers.filter(_ % 2 == 0).sum
  }

  println(s"Result: $result")
  println(s"Execution Time: $time nanoseconds")
}

Combining Scala’s syntax rules for when to leave out parentheses, passing code blocks as arguments, and currying, we can write utility functions that look like built-in functions.

4. When Shouldn’t We Drop the Parentheses

In Scala, the language’s syntax allows for unique flexibility that can sometimes lead to confusing constructs, primarily when blocks are used as method arguments. This flexibility, while powerful, can be a double-edged sword in terms of readability.

Consider an example where we have a simple method process() that takes an integer and performs a basic operation:

object DoubleEdgedSword extends App {
  def process(value: Int): Int = value * 2

  val result = process {
    1 + 2
    5 // This is the actual integer that will be passed to the 'process' method
  }

  println(result)
}

This example highlights a peculiarity of Scala’s syntax, where including additional expressions in a block can lead to confusion. While the code is syntactically correct, the compiler will issue a warning:

[warn] -- [E129] Potential Issue Warning: /Users/oscar/writting/baeldung/scala-tutorials/scala-core-modules/scala-core-8/src/main/scala/com/baeldung/scala/braces/DoubleEdgedSword.scala:7:6
[warn] 7 |    1 + 2
[warn]   |    ^^^^^
[warn]   |A pure expression does nothing in statement position; you may be omitting necessary parentheses
[warn]   |
[warn]   | longer explanation available when compiling with `-explain`
[warn] one warning found

But it compiles without errors; the 1 + 2 expression can make the code harder to understand. This is why we should limit our use of the option to not use parentheses when passing blocks to only those cases where the parameter has a function type.

5. Conclusion

In this tutorial, we learned that Scala doesn’t use braces instead of parentheses. Instead, we can combine different syntactical rules to write code that looks like built-in constructs (such as for or while), and this gives the impression we’re replacing parentheses with braces. We’re simply leaving out the parentheses in a single-argument method, with a block as an argument.

Scala’s flexible syntax and expression-oriented design blend enhances its object-oriented and functional programming capability. The ability to leave parentheses out and pass blocks as arguments, mainly when using higher-order functions, allows us to write utility functions that look like built-in control flow instructions.

As always, the complete source code of the article is available over on GitHub.

Comments are closed on this article!