1. Overview

In this tutorial, we’ll take a look at implicit parameters in Scala and how to use them.

2. What Are Implicit Parameters?

Implicit parameters are similar to regular method parameters, except they could be passed to a method silently without going through the regular parameters list.

A method can define a list of implicit parameters, that is placed after the list of regular parameters. These parameters are indicated using the implicit keyword, and all parameters after the implicit keyword are implicit:

def draw(text: String)(implicit color: Color, by: DrawingDevice)

3. Usage

When a method is defined with implicit parameters, Scala will look up implicit values in the scope by matching the type if they are not already passed in the implicit parameter list.

Let’s say we have two case classes defined, Color and DrawingDevice:

case class Color(value: String)
case class DrawingDevice(value: String)

Now, let’s define a write method that takes a String as a regular parameter and the two case classes as implicit parameters:

def write(text: String)(implicit color: Color, by: DrawingDevice) =
  s"""Writing "$text" in ${color.value} color by ${by.value}."""

To provide the Color and DrawingDevice instances to the write method, we would define the instances as implicit and call the method:

implicit val red: Color = Color("red")
implicit val drawingDevice: DrawingDevice = DrawingDevice("pen")
assert(
    write("A good day") ==
      """Writing "A good day" in red color by pen."""
  )

When Scala matches the implicit values, the parameter and variable names are ignored. As mentioned earlier, Scala will match implicit value by type.

What if more than one implicit variables share the same type? We’ll examine this by modifying the writeByMixColors method:

def writeByMixColors(text: String)(implicit color: Color, color2: Color, by: DrawingDevice) = 
  s"""Writing "$text" in ${color.value} and ${color2.value} colors by ${by.value}."""

In this case, we have 2 implicit parameters (color and color2) both with the type of Color. Scala will match the value red to each of the colors in the parameter list. As a result, the output will be:

Writing "A good day" in red and red colors by pen.

However, what if we define 2 different implicit Color values?

implicit val red: Color = Color("red")
implicit val green: Color = Color("green")
implicit val pen: DrawingDevice = DrawingDevice("pen")

println(writeByMixColors("A good day")) // This won't compile.

This will result in an error message of ambiguous implicit values. red and green are both Color, but Scala won’t know which one of them to match with the parameters.

However, we could pass in implicit values in a non-implicit way:

implicit val red: Color = Color("red")
implicit val green: Color = Color("green")
implicit val pen: DrawingDevice = DrawingDevice("pen")

println(writeByMixColors("A good day")(red, green, pen))

The result will be the following output:

Writing "A good day" in red and green colors by pen.

In this case, the implicit parameters work like regular parameters. However, when we do this, we have to pass in the entire list of implicit parameters, unless there is a default value specified.

Finally, if we call the writeByMixColors method and we haven’t defined any implicit parameters, the compiler will return an error informing us that an implicit value could not be found.

4. Advantages

Appropriate usage of implicit parameters could improve readability and reusability. This allows us to define the values anywhere in our code as long as they are within scope. Additionally, they can be reused across other methods that look for implicit parameters:

implicit val red: Color = Color("red")
implicit val pen: DrawingDevice = DrawingDevice("pen")

assert(
  write("A good day") ==
    """Writing "A good day" in red color by pen."""
)
assert(
  write("Drink a cup of coffee") ==
    """Writing "Drink a cup of coffee" in red color by pen."""
)
assert(
  write("Write some code") ==
    """Writing "Write some code" in red color by pen."""
)

A real-world example is the database’s create, read, update, and delete (CRUD) operations:

// without implicit parameter
model.create(conn, newRecord)

// with implicit parameter
model.create(newRecord)

The crucial part of these operations is the record itself, but often we have to carry the database connection along with the code. And because of that, the database connection is a good candidate for the implicit parameters in CRUD operations.

5. Disadvantages

While implicit parameters help to improve the code design, however, overusing it could bring a negative impact on code readability. They can be less visible than typical parameters. Code reviewers often have to pay more attention to fully understand the code.

The Apache Flink project recommends staying away from these parameters to make code easier to reason about.

6. Conclusion

In this article, we looked at implicit parameters and how they are used.

They can be very useful for designing clean code, but at the same time, we should be aware that overusing them could cause a readability problem.

As always, code examples can be found over on GitHub.

guest
0 Comments
Inline Feedbacks
View all comments