What are All the Uses of an Underscore in Scala?

The underscore (_) is a symbol frequently employed in Scala, serving as a handy tool to simplify and condense code. While it’s dubbed “syntactic sugar” for its ability to streamline code, its extensive use can sometimes lead to confusion and make the learning process more challenging.

This article focuses on discussing the various common uses of underscores in Scala, shedding light on their diverse functionalities.

Table of Content

  • Pattern Matching and Wildcards
  • Ignored Parameter
  • Conversions
  • Miscellaneous Usages
  • Conclusion
  • FAQs

Pattern Matching and Wildcards

The underscore serves as a wildcard and is commonly used in Scala for matching unknown patterns. It’s often one of the earliest applications of the underscore we encounter when we start learning Scala. Now, let’s explore a few examples to understand its usage better.

1. Module Import

  • When we import packages in our code, we sometimes use an underscore to indicate that we want to import all or some members of the module.
  • Importing all members from the package org.junit is done with an underscore, similar to a wildcard import in Java.
  • If we want to import all members except one, say Before, we specify {Before => _}.
  • Renaming a member while importing is also possible. For instance, importing all members but renaming Before to B4 is done with {Before => B4, _}.

Syntax:

import org.junit._
import org.junit.{Before => _, _}
import org.junit.{Before => B4, _}

2. Existential Types

  • The underscore is used as a wildcard to match all types in type creators like List, Array, Seq, Option, or Vector. This allows flexibility in handling various types of data.
  • For instance, in a function to calculate the length of a list of lists (List[List[_]]), we can use _ to accept any type of element in the inner lists.

Below is the Scala program to implement the approach:

Scala
def itemTransaction(price: Double): String = {
  price match {
    case 130.0 => "Buy"
    case 150.0 => "Sell"
    case _ => "Need approval"
  }
}
println(itemTransaction(130)) 
println(itemTransaction(150))
println(itemTransaction(70))
println(itemTransaction(400)) 

Output:


Existential Types

3. Matching

  1. In Scala, the match keyword is used for pattern matching. The underscore symbol serves to catch all possible cases not handled by explicitly defined cases.
  2. Consider a scenario where we decide whether to buy or sell an item based on its price. If the price is $130, we buy; if it’s $150, we sell. For any other price, we need approval.

Below is the Scala program to implement the approach:

Scala
def itemTransaction(price: Double): String = {
  price match {
    case 130 => "Buy"
    case 150 => "Sell"
    case _ => "Need approval"
  }
}
itemTransaction(130) 
itemTransaction(150) 
itemTransaction(70) 
itemTransaction(400) 

Output:

Ignored Parameter

You can think of the underscore as a handy tool in coding that lets you disregard variables and types that aren’t needed anywhere in your code. It’s like saying, “Hey, I know this exists, but I’m not going to use it here, so let’s just move on without worrying about it.

1. Ignored Parameter

Example:

val ints = (1 to 4).map(_ => “Int”)

assertEquals(ints, Vector(“Int”, “Int”, “Int”, “Int”))

Using the underscore, we ignored whatever value we have in the map anonymous function; we just return Int for each element of the range. We may use the anonymized parameter as a placeholder in function. This makes the code clean though less explicit.

Example:

val prices = Seq(10.00, 23.38, 49.82)

val pricesToInts = prices.map(_.toInt)

assertEquals(pricesToInts, Seq(10, 23, 49))

Here, the mapping is equivalent to:

prices.map(x => x.toInt)

We can also use the underscore to access nested collections.

Below is the Scala program to implement the approach:

Scala
val items = Seq(("candy", 2, true), ("cola", 7, false), ("apple", 3, false), ("milk", 4, true))
val itemsToBuy = items
  .filter(_._3)  // filter in only available items (true)
  .filter(_._2 > 3)  // filter in only items with price greater than 3
  .map(_._1)  // return only the first element of the tuple; the item name
// Print the output to verify
println(itemsToBuy)

Output:

Ignored Parameter

We already saw a hidden import in pattern matching, that can also be referenced as ignoring things. We can ignore a given module and import the rest.

Example:

import org.junit.{Before => _, _}

With this, we’ve imported all members of the junit package except (ignoring) Before.

2. Ignored Variable

When we don’t care about variables for constructed entries, we can ignore them using the underscore.

For example, we want only the first element in a split string:

val text = “a,b”

val Array(a, _) = text.split(“,”)

assertEquals(a, “a”)

The same is applicable if we want only the second one:

val Array(_, b) = text.split(“,”)

assertEquals(b, “b”)

We can extend this to more than two entries:

val text = “a,b,c,d,e”

val Array(a, _*) = text.split(“,”)

assertEquals(a, “a”)

To ignore the rest of the entries after the first, we use the underscore together with.

We can also ignore randomly using the underscore for any one entry we don’t want:

val Array(a, b, _, d, e) = text.split(“,”)

assertEquals(a, “a”)

assertEquals(b, “b”)

assertEquals(d, “d”)

assertEquals(e, “e”)

3. Variable Initialization to Its Default Value

When the initial value of a variable is not necessary, we can use the underscore as default:

var x: String = _

x = “real value”

println(x)

This doesn’t work for local variables; local variables must be initialized.

Conversions

1. Function Reassignment

Using the underscore allows transforming a method into a function.

Example:

object Main {

def main(args: Array[String]) {

// Code

}

}

2. Variable Argument Sequence

Converting a sequence into variable arguments is possible with seqName: ‘_’.

Below is the Scala program to implement the approach:

Scala
def sum(args: Int*): Int = {
  args.reduce(_ + _)
}
val sumable = Seq(4, 5, 10, 3)
val sumOfSumable = sum(sumable: _*) // Converting sumable to varargs
assert(sumOfSumable == 22)

Output:

Variable Argument Sequence

3. Partially-Applied Function

Creating a partially applied function involves providing only some arguments. Unprovided parameters are substituted with underscores.

Below is the Scala program to implement the approach:

Scala
def sum(x: Int, y: Int): Int = x + y
val sumToTen = sum(10, _: Int)
val sumFiveAndTen = sumToTen(5)
assert(sumFiveAndTen == 15)

Output:

Partially-Applied Function

Additionally, underscores can be used to ignore parameter groups in functions with multiple parameter groups:

Example:

def bar(x: Int, y: Int)(z: String, a: String)(b: Float, c: Float): Int = x

val foo = bar(1, 2) _ // Ignoring the third parameter group

assert(foo(“Some string”, “Another string”)(3/5, 6/5) == 1)

4. Assignment Operators (Setters overriding)

Overriding default setters is another form of conversion using underscores.

Below is the Scala program to implement the approach:

Scala
class Product {
  private var a = 0
  def price: Int = a
  def price_=(i: Int): Unit = {
    require(i > 10, "Price must be greater than 10")
    a = i
  }
}

val product = new Product
product.price = 20
assert(product.price == 20)

try {
  product.price = 7 // This will fail because 7 is not greater than 10
  assert(false, "Price must be greater than 10")
} catch {
  case _: IllegalArgumentException => assert(product.price != 7)
}

Output:

Assignment Operators (Setters overriding)

Miscellaneous Usages

1. Connecting Letters to Operators or Punctuation

Sometimes, we want to make our variable names clearer by incorporating punctuation. However, unlike alphanumeric characters, we can’t directly use punctuation in variable names. So, we cleverly join letters to punctuation using underscores.

Example:

Scala def list_++(list: List[_]): List[_] = List.concat(list, list)

val concatenatedList = list_++(List(2, 5))

assertEquals(concatenatedList, List(2, 5, 2, 5))

In this snippet, list_++ is a function that concatenates a given list with itself, mimicking the behavior of the ++ operator.

2. Numeric Literal Separator (Scala 2.13+)

Scala 2.13 introduced a handy feature: the ability to use underscores as separators in numeric literals, making them more readable.

Below is the Scala program to implement the approach:

Scala
var x = 1_000_000
println(x) 
var pi = 3.14e-02
println(pi) 
pi = 3.14e-02

Output:

Numeric Literal Separator

Underscores make it easier to parse large numeric literals at a glance.

3. Higher-Kinded Types

Higher-kinded types are types that abstract over other types, allowing for powerful abstractions. In Scala, we can use underscores to define them succinctly.

Below is the Scala program to implement the approach:

Scala
trait ObjectContainer[T[_]] { // higher-kinded type parameter
  def checkIfEmpty[A](collection: T[A]): Boolean
}

object SeqContainer extends ObjectContainer[Seq] {
  override def checkIfEmpty[A](collection: Seq[A]): Boolean = collection.isEmpty
}

var seqIsEmpty = SeqContainer.checkIfEmpty(Seq(7, "7"))
assert(seqIsEmpty == false)

seqIsEmpty = SeqContainer.checkIfEmpty(Seq())
assert(seqIsEmpty == true)

Output:

Higher-Kinded Types

In this example, ObjectContainer defines a trait with a higher-kinded type parameter T[_], allowing it to operate on various collection types. We then implement it for Seq, demonstrating its usage.

Conclusion

Underscores in Scala exemplify the elegance of simplicity, offering a myriad of functionalities that streamline coding, enhance readability, and unlock new possibilities for developers. Embracing the diverse uses of underscores empowers Scala programmers to write cleaner, more expressive, and more robust code.

FAQs

What does the underscore signify in Scala?

The underscore (_) in Scala serves various purposes, acting as a wildcard, placeholder, and facilitator for concise code.

How is the underscore utilized in pattern matching?

In pattern matching, the underscore is used to represent wildcard cases or ignored values, allowing for concise and comprehensive pattern matching.

Can you explain the role of the underscore in function definitions and method invocations?

Certainly! In function definitions, underscores can be used to ignore parameters or create partially applied functions. In method invocations, underscores serve to transform methods into functions or convert sequences into variable arguments.

What are some scenarios where underscores are employed for variable and value manipulation?

Underscores are handy for ignoring unused variables, accessing elements in collections, and providing default values for variables. They also aid in enhancing code readability and simplifying complex operations.

How does Scala utilize underscores for conversions and type definitions?

Scala leverages underscores for various conversions, such as transforming methods into functions, defining higher-kinded types, and even facilitating assignment operators. Underscores play a crucial role in enabling flexibility and adaptability within Scala codebases.



Contact Us