Implicits values are one of the unique features of Scala but they are very complex and easy to misuse. So in this talk we will discuss various valid use cases and anti-pattern for implicits.
You don’t need to be a Scala expert, I will also present how implicit works at high level.
4. Implicit parameters
def newBlogPost(title: String)(implicit author: User): BlogPost
class UserService(db: Connection)(implicit ec: ExecutionContext) {}
When should we define a parameter as implicit?
4
7. Austria
sealed abstract class AcademicTitle(val shortForm: String)
case object MasterOfScience extends AcademicTitle("MSc")
case object Magistrate extends AcademicTitle("Mag")
...
7
8. Austria
sealed abstract class AcademicTitle(val shortForm: String)
case object MasterOfScience extends AcademicTitle("MSc")
case object Magistrate extends AcademicTitle("Mag")
...
def fullName(firstName: String, lastName: String, title: Option[AcademicTitle]): String = {
val suffix = title.map(" " + _.shortForm).getOrElse("")
s"${firstName} ${lastName}${suffix}"
}
fullName("John", "Doe", None)
// res: String = "John Doe"
fullName("Bob", "Smith", Some(MasterOfScience))
// res: String = "Bob Smith MSc"
8
16. Implicit resolution
val ImplicitValues: Map[Type, Value] = // Maintained by the compiler
Map(
Int -> 5,
String -> "",
UserId -> UserId("john_1234"),
)
createEmptyBlogPost("Scala Implicits: The complete guide") // user code
16
17. Implicit resolution
val ImplicitValues: Map[Type, Value] = // Maintained by the compiler
Map(
Int -> 5,
String -> "",
UserId -> UserId("john_1234"),
)
createEmptyBlogPost("Scala Implicits: The complete guide") // user code
|
v
createEmptyBlogPost("Scala Implicits: The complete guide")(ImplicitValues(UserId)) // lookup at compile-time
17
18. Implicit resolution
val ImplicitValues: Map[Type, Value] = // Maintained by the compiler
Map(
Int -> 5,
String -> "",
UserId -> UserId("john_1234"),
)
createEmptyBlogPost("Scala Implicits: The complete guide") // user code
|
v
createEmptyBlogPost("Scala Implicits: The complete guide")(ImplicitValues(UserId)) // lookup at compile-time
18
19. Implicit resolution
val ImplicitValues: Map[Type, Value] = // Maintained by the compiler
Map(
Int -> 5,
String -> "",
UserId -> UserId("john_1234"),
)
createEmptyBlogPost("Scala Implicits: The complete guide") // user code
|
v
createEmptyBlogPost("Scala Implicits: The complete guide")(ImplicitValues(UserId)) // lookup at compile-time
|
v
createEmptyBlogPost("Scala Implicits: The complete guide")(UserId("john_1234"))
19
20. Implicit resolution
val ImplicitValues: Map[Type, Value] = // Maintained by the compiler
Map(
Int -> 5,
String -> "",
// No UserId
)
createEmptyBlogPost("Scala Implicits: The complete guide") // user code
|
v
createEmptyBlogPost("Scala Implicits: The complete guide")(ImplicitValues(UserId)) // lookup at compile-time
|
v
???
20
21. Implicit resolution
val ImplicitValues: Map[Type, Value] = // Maintained by the compiler
Map(
Int -> 5,
String -> "",
// No UserId
)
createEmptyBlogPost("Scala Implicits: The complete guide") // user code
|
v
createEmptyBlogPost("Scala Implicits: The complete guide")(ImplicitValues(UserId)) // lookup at compile-time
|
v
error: could not find implicit value for parameter requesterId: UserId // compile-time error
21
22. Implicit Definition
implicit val requesterId: UserId = UserId("john_1234")
createEmptyBlogPost("Scala Implicits: The complete guide")
// res: BlogPost = BlogPost(
// author = UserId("john_1234"),
// title = "Scala Implicits: The complete guide",
// content = "",
// )
22
23. Implicit Scope
class BlogPostTest extends AnyFunSuite {
test("createEmptyBlogPost gets the author implicitly") {
implicit val requesterId: UserId = UserId("john_1234")
val result = createEmptyBlogPost("Scala Implicits: The complete guide") // ✅ Compile
assert(result.author == requesterId)
}
test("createEmptyBlogPost has no content") {
val result = createEmptyBlogPost("Scala Implicits: The complete guide") // ❌ could not find implicit value
assert(result.content.isEmpty)
}
}
23
24. Implicit Scope
class BlogPostTest extends AnyFunSuite {
test("createEmptyBlogPost gets the author implicitly") {
implicit val requesterId: UserId = UserId("john_1234")
val result = createEmptyBlogPost("Scala Implicits: The complete guide") // ✅ Compile
assert(result.author == requesterId)
}
test("createEmptyBlogPost has no content") {
val result = createEmptyBlogPost("Scala Implicits: The complete guide") // ❌ could not find implicit value
assert(result.content.isEmpty)
}
}
24
25. Implicit Scope
class BlogPostTest extends AnyFunSuite {
implicit val requesterId: UserId = UserId("john_1234")
test("createEmptyBlogPost gets the author implicitly") {
val result = createEmptyBlogPost("Scala Implicits: The complete guide") // ✅ Compile
assert(result.author == requesterId)
}
test("createEmptyBlogPost has no content") {
val result = createEmptyBlogPost("Scala Implicits: The complete guide") // ✅ Compile
assert(result.content.isEmpty)
}
}
25
26. Summary
1. The compiler keeps track of all the implicits available within a scope
2. At compile-time, the compiler injects all implicit parameters
3. If an implicit is missing, we get a compiled-time error
26
27. Summary
1. The compiler keeps track of all the implicits available within a scope
2. At compile-time, the compiler injects all implicit parameters
3. If an implicit is missing, we get a compiled-time error
4. If there are 2 or more implicit values of the same type in scope, we also
get a compiled-time error
27
28. Within a Scope, there must be Only One implicit value per Type
28
30. Environment Pattern
val httpService = {
case req @ POST -> Root / "blog" => // create a blog
case req @ PUT -> Root / "blog" / id => // update a blog
case req @ DELETE -> Root / "blog" / id => // delete a blog
}
30
31. Environment Pattern
case req @ POST -> Root / "blog" =>
implicit val requesterId: UserId = extractRequesterId(req)
for {
payload <- req.parseBodyAs[NewBlog]
_ <- blogAPI.create(payload.title)
} yield Ok()
31
32. Environment Pattern
case req @ POST -> Root / "blog" =>
implicit val requesterId: UserId = extractRequesterId(req)
for {
payload <- req.parseBodyAs[NewBlog]
_ <- blogAPI.create(payload.title)
} yield Ok()
32
33. Environment Pattern
case req @ POST -> Root / "blog" =>
implicit val requesterId: UserId = extractRequesterId(req)
for {
payload <- req.parseBodyAs[NewBlog]
_ <- blogAPI.create(payload.title)
} yield Ok()
class BlogAPI(db: DB) {
def create(title: String)(implicit requesterId: UserId): Future[Unit] = {
val newBlog = createEmptyBlogPost(title)
db.save(newBlog)
}
}
33
34. Environment Pattern
case req @ POST -> Root / "blog" =>
implicit val requesterId: UserId = extractRequesterId(req)
for {
payload <- req.parseBodyAs[NewBlog]
_ <- blogAPI.create(payload.title)
} yield Ok()
class BlogAPI(db: DB) {
def create(title: String)(implicit requesterId: UserId): Future[Unit] = {
val newBlog = createEmptyBlogPost(title)
db.save(newBlog)
}
}
def createEmptyBlogPost(title: String)(implicit requesterId: UserId): BlogPost =
BlogPost(
author = requesterId,
title = title,
content = "",
)
34
35. Environment Pattern
case req @ POST -> Root / "blog" =>
implicit val requesterId: UserId = extractRequesterId(req)
for {
payload <- req.parseBodyAs[NewBlog]
_ <- blogAPI.create(payload.title)
} yield Ok()
class BlogAPI(db: DB) {
def create(title: String)(implicit requesterId: UserId): Future[Unit] = {
val newBlog = createEmptyBlogPost(title)
db.save(newBlog)
}
}
def createEmptyBlogPost(title: String)(implicit requesterId: UserId): BlogPost =
BlogPost(
author = requesterId,
title = title,
content = "",
)
35
65. Implicit Search
Ok(user)
// res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"})
Search for JsonEncoder[User] in User companion object
case class User(userId: UserId, email: String)
object User {
implicit val encoder: JsonEncoder[User] = ...
}
65
66. Implicit Search
Ok(userIds)
// res: HttpResponse(200, ["1234", "9464", "0582"])
Search for JsonEncoder[List[UserId]] in List companion object
sealed trait List[A]
object List {
// There is no JsonEncoder here
}
66
67. Implicit Search
Ok(userIds)
// res: HttpResponse(200, ["1234", "9464", "0582"])
Search for JsonEncoder[List[UserId]] in JsonEncoder companion object
object JsonEncoder {
implicit val string: JsonEncoder[String] = ...
implicit val int : JsonEncoder[Int] = ...
implicit val date : JsonEncoder[LocalDate] = ...
implicit val list : JsonEncoder[List[???]] = ...
}
67
68. Implicit Derivation
Ok(userIds)
// res: HttpResponse(200, ["1234", "9464", "0582"])
Search for JsonEncoder[List[UserId]] in JsonEncoder companion object
object JsonEncoder {
implicit val string: JsonEncoder[String] = ...
implicit val int : JsonEncoder[Int] = ...
implicit val date : JsonEncoder[LocalDate] = ...
implicit def list[A]: JsonEncoder[List[A]] = ...
}
68
69. Implicit Derivation
Ok(userIds)
// res: HttpResponse(200, ["1234", "9464", "0582"])
Search for JsonEncoder[List[UserId]] in JsonEncoder companion object
object JsonEncoder {
implicit val string: JsonEncoder[String] = ...
implicit val int : JsonEncoder[Int] = ...
implicit val date : JsonEncoder[LocalDate] = ...
implicit def list[A](implicit valueEncoder: A): JsonEncoder[List[A]] = ...
}
69
70. Implicit Search for JsonEncoder[List[UserId]]
1. Search current scope ❌
2. Search companion object of List ❌
3. Search companion object of JsonEncoder (Maybe)
4. Search companion object of UserId ✅
70
71. Typeclass Pattern
def Ok[A](value: A)(implicit encoder: JsonEncoder[A]): HttpResponse
Ok(user)
// res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"})
Ok(userIds)
// res: HttpResponse(200, ["1234", "9464", "0582"])
Ok((x: Int) => x + 1) // ❌ compile-time error: could not find implicit value for parameter
71
72. Typeclass Pattern
def Ok[A: JsonEncoder](value: A): HttpResponse
Ok(user)
// res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"})
Ok(userIds)
// res: HttpResponse(200, ["1234", "9464", "0582"])
Ok((x: Int) => x + 1) // ❌ compile-time error: could not find implicit value for parameter
72
74. Typeclass Pattern
For types we own, define instances in the companion object
object User {
implicit val encoder: JsonEncoder[User] = ...
}
object UserId {
implicit val encoder: JsonEncoder[UserId] = ...
}
For other types, define instances in the companion object of the typeclass
object JsonEncoder {
implicit val string: JsonEncoder[String] = ...
implicit val int : JsonEncoder[Int] = ...
implicit val date : JsonEncoder[LocalDate] = ...
}
74