Field Notes

Scala/Play, Part 10: Scala 2 Implicit Parameters - What the Compiler Supplies, and Why It Refuses

Scala/Play, Part 10: Scala 2 Implicit Parameters - What the Compiler Supplies, and Why It Refuses

I kept seeing implicit in Scala code at work. Coming from Java and Spring, I wanted to understand what the compiler was actually doing. Our policy repository gave me a small enough example to follow every argument without introducing a framework.

This lesson uses Scala 2.13.18. We start with multiple parameter lists, let the compiler supply a repository, then deliberately trigger two compilation failures. All policy data is fictional.

Zakaria's navy-hoodie mascot considering a telescope eyepiece beside two alternatives in an observatory
Choosing the component explicitly. The diagrams below distinguish that choice from compiler-supplied arguments.

First, separate the parameter lists

Part 9 introduced PolicyRepository, whose find method returns Option[PolicySnapshot]. Our caller originally took both inputs together: describe(number, repository). Scala also allows:

def describe(number: String)(repository: PolicyRepository): String =
  repository.find(number)
    .map(policy => "Found policy " + policy.number)
    .getOrElse("Policy not found")

describe("POL-001")(repository)

This is one method with two parameter lists. Both names are available in its body. The complete call still returns String, and I still supply both arguments explicitly. Grouping the dependency separately prepares the next change.

Implicit marks a parameter the compiler may supply

def describe(number: String)
            (implicit repository: PolicyRepository): String =
  repository.find(number)
    .map(policy => "Found policy " + policy.number)
    .getOrElse("Policy not found")

The body has not changed. The implicit keyword marks the second parameter list. To make a suitable value available at a call site, our example declares:

implicit val localStore: PolicyRepository =
  new InMemoryPolicyRepository(
    List(PolicySnapshot("POL-001", 600, 3))
  )

describe("POL-001")(localStore) // supplied explicitly
describe("POL-001")             // supplied by the compiler

Both calls return "Found policy POL-001". For the second call, the compiler selects localStore and inserts it as the missing argument. The parameter is named repository and the value is named localStore: those names need not match.

The two declarations have complementary roles. The implicit parameter requests a dependency; the implicit val makes this value eligible for search. The choice is resolved during compilation. At runtime, the method receives an ordinary repository object. This is not a Spring container searching for a bean during the call.

A call omitting the repository argument becomes a call with localStore supplied, while the method body remains unchanged
The argument is omitted in source, not absent from the method invocation.

An explicit argument still works

val emptyStore: PolicyRepository =
  new InMemoryPolicyRepository(List.empty[PolicySnapshot])

describe("POL-001")(emptyStore)
// "Policy not found"

Even with localStore available implicitly, this call uses the explicitly supplied emptyStore. The ordinary val needs no implicit modifier when I pass it myself.

Failure one: no eligible implicit repository

In a separate method, with no eligible implicit repository available, this fails:

val store: PolicyRepository =
  new InMemoryPolicyRepository(List.empty[PolicySnapshot])

ImplicitParameterLesson.describe("POL-001")

The compiler does not automatically treat every local variable of the right type as an implicit candidate. Our actual diagnostic was:

could not find implicit value for parameter repository: learning.PolicyRepository

I can pass store explicitly, or mark the intended declaration implicit. Also, a local implicit inside another method's body is not visible at this call site. Scope matters. Scala's broader search rules also consider associated companion objects; this lesson deliberately uses local values and leaves detailed precedence for later.

Failure two: equally suitable candidates

implicit val first: PolicyRepository =
  new InMemoryPolicyRepository(
    List(PolicySnapshot("POL-001", 600, 3))
  )

implicit val second: PolicyRepository =
  new InMemoryPolicyRepository(List.empty[PolicySnapshot])

ImplicitParameterLesson.describe("POL-001")

These two values have the same declared type and are in the same scope. The compiler refused the call:

ambiguous implicit values:
 both value second of type learning.PolicyRepository
 and value first of type learning.PolicyRepository
 match expected type learning.PolicyRepository

It does not choose by declaration order or inspect which repository contains the policy. Multiple candidates are not universally ambiguous; suitability and precedence matter. These two are equally suitable in this example.

The working fix selects one:

ImplicitParameterLesson.describe("POL-001")(second)
// "Policy not found"

Alternatively, I can arrange the scope so only the intended candidate remains eligible.

Missing and ambiguous implicit arguments fail compilation; a supplied repository returning None is a runtime lookup outcome
A missing dependency and a missing policy happen at different stages.

Reproduce the failures without breaking the lab

The failing sources live outside src in examples/implicit-errors. Start sbt from the repository root, then enter:

set Compile / unmanagedSources += baseDirectory.value / "examples" / "implicit-errors" / "MissingRepository.scala"
compile
exit

set changes the build for that session; it does not edit build.sbt. Compile / unmanagedSources lists ordinary main source files, and += adds this one. The slash between path components joins them relative to the project root.

Start a fresh sbt session and substitute AmbiguousRepository.scala to reproduce the second failure. Using the sbt prompt avoided a Windows launcher quoting issue we encountered when passing this expression directly on the command line.

After those expected failures, a fresh normal run exercises the successful examples and fixes:

sbt "runMain learning.PremiumLesson"

Both compiler diagnostics were verified, then the normal lab passed all assertions. The runnable fixes are in ImplicitSearchLesson.scala.

I can now read an omitted argument as a concrete compiler decision, and distinguish an unsuccessful search from a repository returning None. Next: implicit classes, which explain extension-style methods.

Reference: Scala Tour: implicit parameters, using its Scala 2 examples. Our target remains Scala 2.13.18, Play 3 and Apache Pekko.