1. Overview

Everyone studying or using the Scala programming language comes across the implicitly sooner or later. However, very few stop to deeply understand the meaning of it.

Hence, this article will analyze the semantics of the implicitly function and its main uses.

2. What “Implicitly” Stands For

Imagine we’re writing a function that returns the weight of an object, given its mass and the value of the gravitational constant on a planet:

def weight(mass: Double, gravitationalConstant: Double): Double =
  mass * gravitationalConstant

After starting to use the weight function a little, we understand that passing the gravitational constant value explicitly every time is redundant.

Moreover, it violates the KISS (Keep It Simple, Stupid!) principle.

However, we could use a compiler to fill the gravitational constant value for us. We can use implicit parameters for that:

def weightUsingImplicit(mass: Double)(implicit gravitationalConstant: Double): Double =
  weight(mass, gravitationalConstant)

Now, all that we have to do is provide the value of the gravitational constant as an implicit value in the resolution scope of the weightUsingImplicit function (see Implicit Parameters in Scala for further details on implicit parameters):

implicit val G: Double = 9.81

However, we feel like we can do even better. We don’t like the fact we have to specify the curried parameter gravitationalConstant explicitly. What kind of tools does Scala empower us with?

In this regard, version 2.8 of Scala introduced a new function in the Predef package, which is always available since the compiler imports it by default:

def implicitly[T](implicit e: T) = e

Basically, implicitly* works as a “compiler for implicits”. We can verify if there is an implicit value of type *T. If no implicit value of type T is available in the scope, the compiler will warn us of the fact.

How can we use our sparkly new tool to improve the readability of the weight function? We can remove the implicit curried parameter and then introduce the use of implicitly:

def weightUsingImplicitly(mass: Double): Double = {
  val gravitationalConstant = implicitly[Double]
  weight(mass, gravitationalConstant)
}

Basically, we ask the compiler to find an implicit value of type Double and use it as the value of the variable gravitationalConstant. Cool.

However, the example above isn’t the common use of implicitly in Scala. In fact, it’s one of the bricks that make up the type classes pattern. Let’s introduce it.

3. Type Classes: Unleashing the Real Power of the Implicit Resolution

3.1. Type Classes 101

It is well known that many of the concepts we find in Scala come from the Haskell programming language. Indeed, the type-classes pattern is one of them.

Basically, a type class represents a behavior or a characteristic that a class of a generic type T could have. It’s a concept similar to interfaces, and in fact, it is represented in Scala using a trait:

trait Searchable[T] {
  def uri(obj: T): String
}

For a deeper dive into traits, please review the article Introduction to Traits in Scala.

The example above defines a type class, allowing a type T to be searchable — that is, having an associated URI. Every class that wants to participate in the Searchable type class must implement its abstract method uri.

Type classes represent how Scala (and Haskell, of course) implements the so-called ad-hoc polymorphism. Indeed, every function receiving types implementing the Searchable trait can have a polymorphic behavior:

def searchWithImplicit[S](obj: S)(implicit searchable: Searchable[S]): String = searchable.uri(obj)

Imagine we have two types, Customer and Policy, that we want to make searchable:

case class Customer(taxCode: String, name: String, surname: String)
case class Policy(policyId: String, description: String)

The first thing we need to do is to implement the uri method for the above two types. So, let’s implement the Searchable trait using anonymous classes:

implicit val searchableCustomer: Searchable[Customer] = new Searchable[Customer] {
  override def uri(customer: Customer): String = s"/customers/${customer.taxCode}"
}
implicit val searchablePolicy: Searchable[Policy] = new Searchable[Policy] {
  override def uri(policy: Policy): String = s"/policies/${policy.policyId}"
}

Due to the compiler’s implicit resolution, the searchWithImplicit method’s behavior will be polymorphic because it will change according to the resolved instance of the type class.

If we invoke the method using a Customer, the compiler resolves the type class using the variable searchableCustomer, using the variable searchablePolicy otherwise.

To have more information on the type classes pattern, let’s refer to the article Type Classes in Scala.

3.2. Type Classes and the Implicitly Function

Ok, type classes are cool, but where does the implicitly function come in? As we said, we can use the implicitly as a “compiler for implicits”.

However, this time we don’t want to resolve a type T, such as the Customer or Policy types, directly. Indeed, we need to resolve the type Searchable[T], if any*.*

To overcome this problem, Scala introduces the so-called “context-bound” on types from version 2.8. Basically, it’s a constraint that must be true on a type T, and we can view its syntax in a variant of our searchWithImplicit function:

def searchWithContextBound[S: Searchable](obj: S): String

As we can notice, the context-bound is on type S and tells the compiler that a type Searchable[S] must exist.

Since, due to the context-bound, we know for sure that a type Searchable[S] is available in the body of the parametric function, we can now use the implicitly function to retrieve it:

def searchWithContextBound[S: Searchable](obj: S): String = {
  val searchable = implicitly[Searchable[S]]
  searchable.uri(obj)
}

As we can see, the signature of the searchWithContextBound function is now cleaner than the signature of the searchWithImplicit function since it exposes only the business parameters, completely hiding the type class and the implicit resolution mechanism.

Summing up, the type classes pattern allows us to have two different implementations of the searchWithContextBound function, one for the Customer type and one for the Policy type:

val customer = Customer("123456", "Will", "Smith")
val uri = searchWithContextBound(customer)
assert(uri == "/customers/123456")

val policy = Policy("09876", "A policy")
val uri = searchWithContextBound(policy)
assert(uri == "/policies/09876")

Last but not least, if we want to add a new polymorphic behavior of the function for a new fresh type, add a new anonymous implementation of the Searchable trait and let the magic happen!

4. Conclusion

In this article, we introduced the function implicitly, available since version 2.8 of Scala. First, we started showing the basic usage of the function as a “compiler for implicits”.

Finally, we saw how we could use the implicitly function inside the type classes pattern to make it cleaner and more readable.

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


« 上一篇: Alpakka简介
» 下一篇: Scala中的重复参数