1. Introduction

One of the most powerful and widely discussed features of Scala is its implicit functionality. As powerful as it may be, there are still many complaints regarding its complexities, even for experienced developers. Due to these problems, the Scala compiler team redesigned the implicit in the new Scala 3, making it more clear and easier to use.

In this tutorial, we’ll learn how the usage of implicit has been redesigned in Scala 3.

2. Scala 2 implicit Drawbacks

The major drawbacks with implicit in Scala 2 are:

  • Usage of the keyword implicit for unrelated functionalities
  • Unintended conversions due to implicit conversions
  • Unclear compiler error messages

Due to these, even experienced developers sometimes find it quite difficult to debug some of the issues related to implicit. As a result, in Scala 3, one of the major focuses was to redesign the existing implicit functionality.

3. New Keywords

As part of the redesign, two new keywords are introduced in Scala 3 in place of implicit. However, the implicit keyword is still supported in Scala 3 and will be removed in later releases.

3.1. given

The given keyword is used to define an instance of implicit value:

given timeout: Int = 10 

In Scala 3, it’s not required to name a given instance. We can directly assign a value without the name:

given Int = 10

3.2. using

The using keyword is used to pass an implicit parameter to a method:

def execute(url:String)(using timeout: Int):String = "{}"

We can also explicitly provide the implicit value as:

execute("http://www.baeldung.com")(using 4)

4. Summon an implicit Value From Scope

In Scala 2, we can use the implicitly method to summon an available implicit value from the scope. For example, to get an execution context from the scope, we can write:

val ctx = implicitly[ExecutionContext]

In Scala 3, this method is removed and is replaced with summon:

val ctx = summon[ExecutionContext]

5. Implicit Conversion

Implicit conversion is a way to convert a value from one data type to another without any explicit transformation.

Let’s assume that we have a requirement to handle processing-time data. We can define the method as:

case class Second(value: Int)
object TimeUtil {
  def doSomethingWithProcessingTime(sec: Second): String = {
    // impl logic
    s"${sec.value} seconds"
  }
}

If we have the value in seconds, we need to wrap it in the case class, Second, before invoking doSomethingWithProcessingTime(). The implicit conversion provides a way to avoid this explicit type conversion.

5.1. Usage in Scala 2

Now, let’s see how we implement implicit conversion in Scala 2. We can define a method with the implicit keyword:

object ImplicitConversion {
  implicit def intToSecond(value: Int): Second = Second(value)
}

Before invoking the doSomethingWithProcessingTime(), we can bring the implicit conversion to the scope. After that, we can just invoke the method with an Int value instead of wrapping it in Second:

import ImplicitConversions._
val processingTime = 100
//auto conversion from Int to Second using intToSecond() from scope
TimeUtil.doSomethingWithProcessingTime(processingTime)

Even though this usage looks simple, implicit conversions can cause unintended errors in large projects. We might import these implicit conversions unknowingly and cause unexpected behaviors.

5.2. Usage in Scala 3

To avoid this drawback, Scala 3 introduced the Conversion type class. We can define it using the given keyword:

object ImplicitConversion {
  given Conversion[Int, Second] = Second(_)
}

The above code defines a Conversion instance from Int to Second. We can import the given instance, and it will implicitly convert the Int value to Second:

object Usage {
  import ImplicitConversion.given
  val processingTime = 100
  //auto conversion from Int to Second using given
  TimeUtil.doSomethingWithProcessingTime(processingTime)
}

Note that it’s required to use ImplicitConversion.given to bring the conversion into the scope. Even if we use the wildcard to import, it will not bring the given instances to the scope. This way, we won’t accidentally import given instances to the scope.

We can also implement a conversion from Second to Int:

given Conversion[Second, Int] = _.value

6. Extension Methods

An extension method allows us to add additional methods to a third-party type. Also known as the Pimp My Library Pattern, it’s a very powerful way to extend existing types without modifying them.

6.1. Usage in Scala 2

In Scala 2, we implement the extension method using the implicit keyword. For instance, if we want to easily convert an Int value to the Second case class, we can use an implicit class:

object Extension {
  implicit class IntExtension(value: Int) {
    def toSecond() = Second(value)
  }
}

Now, we can import the implicit class and use the toSecond() method as if it is defined on the Int class:

import Extension._
val second: Second = 100.toSecond()

6.2. Usage in Scala 3

Scala 3 introduced the extension keyword to implement an extension method:

object Extension {
  extension(sec: Int) def toSecond() = Second(sec)
}

We can import and use the extension method in the same way as in Scala 2:

import Extension._
val sec: Second = 10.toSecond()

We can also use type arguments with extension methods. Let’s say we want to create an extension method for any numeric values:

object NumericExtensions {
  extension [T:Numeric](v1: T)
    def add(v2: T): T = summon[Numeric[T]].plus(v1,v2)
}

7. Conclusion

In this article, we discussed the redesign of implicit functionality in Scala 3.

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

Comments are closed on this article!