SlideShare a Scribd company logo
1
Julien Truffaut
Backend Scala developer for 10+ years
FP instructor at fp-tower.com
Co-founder of scalajobs.com
2
Implicit parameters
def newBlogPost(title: String)(implicit author: User): BlogPost
class UserService(db: Connection)(implicit ec: ExecutionContext) {}
3
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
Parameters definition
Explicit
def fullName(firstName: String, lastName: String): String =
s"$firstName $lastName"
fullName("John", "Doe")
// res0: String = "John Doe"
5
Parameters definition
Explicit
def fullName(firstName: String, lastName: String): String =
s"$firstName $lastName"
fullName("John", "Doe")
// res0: String = "John Doe"
fullName(lastName = "Smith", firstName = "Bob")
// res1: String = "Bob Smith"
6
Austria
sealed abstract class AcademicTitle(val shortForm: String)
case object MasterOfScience extends AcademicTitle("MSc")
case object Magistrate extends AcademicTitle("Mag")
...
7
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
Parameters definition
Default value
def fullName(firstName: String, lastName: String, title: Option[AcademicTitle] = None): String = {
val suffix = title.map(" " + _.shortForm).getOrElse("")
s"${firstName} ${lastName}${suffix}"
}
fullName("John", "Doe")
// res: String = "John Doe"
fullName("Bob", "Smith", Some(MasterOfScience))
// res: String = "Bob Smith MSc"
9
Parameters definition
Default value
def kafkaListener(
topic : String,
bootstrapServers : String,
commitTimeout : FiniteDuration = 50.millis,
pollInterval : FiniteDuration = 15.millis,
autoOffsetReset : AutoOffsetReset = AutoOffsetReset.Latest,
)
10
Parameters definition
Default value
def kafkaListener(
topic : String,
bootstrapServers : String,
commitTimeout : FiniteDuration = 50.millis,
pollInterval : FiniteDuration = 15.millis,
autoOffsetReset : AutoOffsetReset = AutoOffsetReset.Latest,
)
Similar options: Overloads or Builder Pattern
11
Parameters definition
Implicit
case class BlogPost(
author : UserId,
title : String,
content : String,
)
case class UserId(value: String)
12
Parameters definition
Implicit
def createEmptyBlogPost(title: String)(implicit requesterId: UserId): BlogPost =
BlogPost(
author = requesterId,
title = title,
content = ""
)
13
Parameters definition
Implicit
def createEmptyBlogPost(title: String)(implicit requesterId: UserId): BlogPost =
BlogPost(
author = requesterId,
title = title,
content = ""
)
createEmptyBlogPost("Scala Implicits: The complete guide")(UserId("john_1234")) // Explicit call
// res: BlogPost = BlogPost(
// author = UserId("john_1234"),
// title = "Scala Implicits: The complete guide",
// content = "",
// )
14
Parameters definition
Implicit
def createEmptyBlogPost(title: String)(implicit requesterId: UserId): BlogPost =
BlogPost(
author = requesterId,
title = title,
content = ""
)
createEmptyBlogPost("Scala Implicits: The complete guide") // Implicit call
// res: BlogPost = BlogPost(
// author = UserId("john_1234"),
// title = "Scala Implicits: The complete guide",
// content = "",
// )
15
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
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
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
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
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
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
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
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
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
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
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
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
Within a Scope, there must be Only One implicit value per Type
28
Environment Pattern
29
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
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
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
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
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
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
Environment Pattern
case class BlogPost(
author : UserId,
title : String,
content : String,
createAt: Instant,
)
36
Environment Pattern
case class BlogPost(
author : UserId,
title : String,
content : String,
createAt: Instant,
)
def createEmptyBlogPost(title: String)(implicit requesterId: UserId): BlogPost =
BlogPost(
author = requesterId,
title = title,
content = "",
createAt = Instant.now(),
)
37
Environment Pattern
test("create blog post") {
implicit val requesterId: UserId = UserId("john_1234")
val result = createEmptyBlogPost("Test")
assert(result == BlogPost(requesterId, "Test", "", ???))
}
38
Environment Pattern
trait Clock {
def now(): Instant
}
39
Environment Pattern
trait Clock {
def now(): Instant
}
object Clock {
val real: Clock = new Clock {
def now(): Instant = Instant.now()
}
def static(timestamp: Instant): Clock = new Clock {
def now(): Instant = timestamp
}
}
40
Environment Pattern
trait Clock {
def now(): Instant
}
object Clock {
val real: Clock = new Clock {
def now(): Instant = Instant.now()
}
def static(timestamp: Instant): Clock = new Clock {
def now(): Instant = timestamp
}
}
41
Environment Pattern
def createEmptyBlogPost(title: String)(implicit requesterId: UserId, clock: Clock): BlogPost =
BlogPost(
author = requesterId,
title = title,
content = "",
createAt = clock.now(),
)
42
Environment Pattern
def createEmptyBlogPost(title: String)(implicit requesterId: UserId, clock: Clock): BlogPost =
BlogPost(
author = requesterId,
title = title,
content = "",
createAt = clock.now(),
)
object Main extends App {
implicit val clock: Clock = Clock.real
...
}
class BlogPostTest extends AnyFunSuite {
implicit val clock: Clock = Clock.static(Instant.EPOCH)
...
}
43
Environment Pattern
object Main extends App {
implicit val ec: ExecutionContext = ExecutionContext.global
...
}
class BlogDatabaseTest extends AnyFunSuite {
implicit val ec: ExecutionContext = fixedSizeExecutionContext(1)
...
}
44
Environment parameters are Static in a given Context
45
Context: Per request
RequesterId
TraceId
Context: Prod / Test
Clock
ExecutionContext (for Future)
RandomGenerator
46
Advice
1. Use precise types
def createQueue[A](implicit size: Int): Queue[A] =
...
def connect(hostname: String)(implicit port: Int): Unit =
...
47
Advice
1. Use precise types
def createEmptyBlogPost(title: String)(implicit requesterId: UserId): BlogPost =
BlogPost(
author = requesterId,
title = title,
content = "",
)
def shareBlog(blog: BlogPost, targetUser: UserId)(implicit requesterId: UserId): Future[Unit] =
...
48
Advice
1. Use precise types
case class RequesterId(userId: UserId)
def createEmptyBlogPost(title: String)(implicit requesterId: RequesterId): BlogPost =
BlogPost(
author = requesterId.userId,
title = title,
content = "",
)
49
Advice
2. Use clear and simple Context
50
The value of implicit parameters must be Obvious
51
Environment Pattern Alternatives
Reader
def createBlogPost(title: String) : Reader[(RequesterId, Clock), BlogPost] = ...
def createProject(projectName: String): Reader[(RequesterId, Clock), Project] = ...
for {
blog <- createBlogPost("Implicits for the noob")
project <- createProject("tutorial")
} yield ...
52
Environment Pattern Alternatives
Reader
def createBlogPost(title: String) : Reader[(RequesterId, Clock), BlogPost] = ...
def createProject(projectName: String): Reader[RequesterId , Project] = ...
for {
blog <- createBlogPost("Implicits for the noob")
project <- createProject("tutorial") ❌
} yield ...
53
Environment Pattern Alternatives
ZIO
trait ZIO[-R, +E, +A]
54
Typeclass Pattern
55
Typeclass Pattern
val user : User = ...
val userIds: List[UserId] = ...
Ok(user)
// res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"})
Ok(userIds)
// res: HttpResponse(200, ["1234", "9464", "0582"])
56
Typeclass Pattern
val user : User = ...
val userIds: List[UserId] = ...
Ok(user)
// res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"})
Ok(userIds)
// res: HttpResponse(200, ["1234", "9464", "0582"])
def Ok[A](value: A): HttpResponse =
HttpResponse(200, value.toJson)
57
Typeclass Pattern
val user : User = ...
val userIds: List[UserId] = ...
Ok(user)
// res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"})
Ok(userIds)
// res: HttpResponse(200, ["1234", "9464", "0582"])
def Ok[A](value: A): HttpResponse =
HttpResponse(200, value.toJson)
58
Typeclass Pattern
val user : User = ...
val userIds: List[UserId] = ...
Ok(user)
// res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"})
Ok(userIds)
// res: HttpResponse(200, ["1234", "9464", "0582"])
def Ok[A](value: A): HttpResponse =
HttpResponse(200, value.toJson)
Ok((x: Int) => x + 1) // ???
59
Typeclass Pattern
trait JsonEncoder[A] {
def encode(value: A): Json
}
60
Typeclass Pattern
trait JsonEncoder[A] {
def encode(value: A): Json
}
def Ok[A](value: A, encoder: JsonEncoder[A]): HttpResponse =
HttpResponse(200, encoder.encode(value))
61
Typeclass Pattern
trait JsonEncoder[A] {
def encode(value: A): Json
}
def Ok[A](value: A, encoder: JsonEncoder[A]): HttpResponse =
HttpResponse(200, encoder.encode(value))
val userEncoder : JsonEncoder[User] = ...
val listUserIdsEncoder: JsonEncoder[List[UserId]] = ...
Ok(user, userEncoder)
// res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"})
Ok(userIds, listUserIdsEncoder)
// res: HttpResponse(200, ["1234", "9464", "0582"])
62
Typeclass Pattern
trait JsonEncoder[A] {
def encode(value: A): Json
}
def Ok[A](value: A)(implicit encoder: JsonEncoder[A]): HttpResponse =
HttpResponse(200, encoder.encode(value))
implicit val userEncoder : JsonEncoder[User] = ...
implicit val listUserIdsEncoder: JsonEncoder[List[UserId]] = ...
Ok(user)
// res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"})
Ok(userIds)
// res: HttpResponse(200, ["1234", "9464", "0582"])
63
Typeclass Pattern
object JsonSerializer {
implicit val userEncoder : JsonEncoder[User] = ...
implicit val listUserIdsEncoder: JsonEncoder[List[UserId]] = ...
}
import JsonSerializer._
Ok(user)
// res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"})
64
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
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
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
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
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
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
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
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
Typeclass Pattern
trait JsonEncoder[A] {
def encode(value: A): Json
}
73
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
Typeclass Pattern
Use typeclass derivation to automatically create instances
object JsonEncoder {
implicit def list[A](implicit valueEncoder: A): JsonEncoder[List[A]] = ...
}
75
Typeclass Pattern
Use typeclass derivation to automatically create instances
object JsonEncoder {
// shapeless, magnolia
implicit def caseClass[A: JsonEncoder, B: JsonEncoder]: JsonEncoder[CaseClass[A, B]] = ...
}
case class User(userId: UserId, email: String)
object User {
implicit val encoder: JsonEncoder[User] = JsonEncoder.caseClass
}
76
Typeclass Pattern (Scala 3)
Use typeclass derivation to automatically create instances
case class User(userId: UserId, email: String) derives JsonEncoder
77
Advice
Don't define instances in custom objects
object Client1Encoder {
implicit val userEncoder: JsonEncoder[User] = ...
}
object Client2Encoder {
implicit val userEncoder: JsonEncoder[User] = ...
}
import Client1Encoder._
// lots of code
Ok(user)
78
The value of implicit parameters must be Obvious
79
Ordering
trait Ordering[A] {
def compare(left: A, right: A): Int
}
80
Ordering
trait Ordering[A] {
def compare(left: A, right: A): Int
}
trait List[A] {
def sorted(implicit ordering: Ordering[A]): List[A] = ...
}
81
Ordering
trait Ordering[A] {
def compare(left: A, right: A): Int
}
trait List[A] {
def sorted(implicit ordering: Ordering[A]): List[A] = ...
}
List(8,1,9).sorted
// ???
82
Ordering
trait Ordering[A] {
def compare(left: A, right: A): Int
}
trait List[A] {
def sorted(implicit ordering: Ordering[A]): List[A] = ...
}
List(8,1,9).sorted
// res3: List[Int] = List(1, 8, 9)
83
Custom Ordering
implicit val descendingIntOrdering: Ordering[Int] = Ordering.Int.reverse
List(8,1,9).sorted
// res4: List[Int] = List(9, 8, 1)
84
Custom Ordering
implicit val descendingIntOrdering: Ordering[Int] = Ordering.Int.reverse
List(8,1,9).sorted
// res4: List[Int] = List(9, 8, 1)
List(8,1,9).sorted(Ordering.Int.reverse)
// res5: List[Int] = List(9, 8, 1)
85
Better Ordering
trait List[A] {
def sorted(ordering: Ordering[A]): List[A] = ...
}
List(8,1,9).sorted(Ordering.Int.ascending)
// res: List[Int] = List(1, 8, 9)
List(8,1,9).sorted(Ordering.Int.descending)
// res: List[Int] = List(9, 8, 1)
86
Review
Explicit
Simple
Great tooling
Default value
When one value is much more common than the others
Implicit
When some parameters value are static within a context
When writing generic functions
87
The value of implicit parameters must be Obvious
88
89
hello@scalajobs.com
90
91

More Related Content

What's hot

Mocking in Java with Mockito
Mocking in Java with MockitoMocking in Java with Mockito
Mocking in Java with Mockito
Richard Paul
 

What's hot (20)

Kotlin Bytecode Generation and Runtime Performance
Kotlin Bytecode Generation and Runtime PerformanceKotlin Bytecode Generation and Runtime Performance
Kotlin Bytecode Generation and Runtime Performance
 
Railway Oriented Programming
Railway Oriented ProgrammingRailway Oriented Programming
Railway Oriented Programming
 
Working Effectively with Legacy Code
Working Effectively with Legacy CodeWorking Effectively with Legacy Code
Working Effectively with Legacy Code
 
Kotlin Coroutines in Practice @ KotlinConf 2018
Kotlin Coroutines in Practice @ KotlinConf 2018Kotlin Coroutines in Practice @ KotlinConf 2018
Kotlin Coroutines in Practice @ KotlinConf 2018
 
Exploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in ScalaExploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in Scala
 
Better than you think: Handling JSON data in ClickHouse
Better than you think: Handling JSON data in ClickHouseBetter than you think: Handling JSON data in ClickHouse
Better than you think: Handling JSON data in ClickHouse
 
From object oriented to functional domain modeling
From object oriented to functional domain modelingFrom object oriented to functional domain modeling
From object oriented to functional domain modeling
 
An Introduction to Gradle for Java Developers
An Introduction to Gradle for Java DevelopersAn Introduction to Gradle for Java Developers
An Introduction to Gradle for Java Developers
 
Introduction to Spring WebFlux #jsug #sf_a1
Introduction to Spring WebFlux #jsug #sf_a1Introduction to Spring WebFlux #jsug #sf_a1
Introduction to Spring WebFlux #jsug #sf_a1
 
Le Wagon - React 101
Le Wagon - React 101Le Wagon - React 101
Le Wagon - React 101
 
Le Wagon - UI and Design Crash Course
Le Wagon - UI and Design Crash CourseLe Wagon - UI and Design Crash Course
Le Wagon - UI and Design Crash Course
 
Big picture of category theory in scala with deep dive into contravariant and...
Big picture of category theory in scala with deep dive into contravariant and...Big picture of category theory in scala with deep dive into contravariant and...
Big picture of category theory in scala with deep dive into contravariant and...
 
What is the State of my Kafka Streams Application? Unleashing Metrics. | Neil...
What is the State of my Kafka Streams Application? Unleashing Metrics. | Neil...What is the State of my Kafka Streams Application? Unleashing Metrics. | Neil...
What is the State of my Kafka Streams Application? Unleashing Metrics. | Neil...
 
Infinitic: Building a Workflow Engine on Top of Pulsar - Pulsar Summit NA 2021
 Infinitic: Building a Workflow Engine on Top of Pulsar - Pulsar Summit NA 2021 Infinitic: Building a Workflow Engine on Top of Pulsar - Pulsar Summit NA 2021
Infinitic: Building a Workflow Engine on Top of Pulsar - Pulsar Summit NA 2021
 
Introduction to Coroutines @ KotlinConf 2017
Introduction to Coroutines @ KotlinConf 2017Introduction to Coroutines @ KotlinConf 2017
Introduction to Coroutines @ KotlinConf 2017
 
Domain Driven Design with the F# type System -- NDC London 2013
Domain Driven Design with the F# type System -- NDC London 2013Domain Driven Design with the F# type System -- NDC London 2013
Domain Driven Design with the F# type System -- NDC London 2013
 
Lambdas and Streams Master Class Part 2
Lambdas and Streams Master Class Part 2Lambdas and Streams Master Class Part 2
Lambdas and Streams Master Class Part 2
 
Real-time Stream Processing with Apache Flink
Real-time Stream Processing with Apache FlinkReal-time Stream Processing with Apache Flink
Real-time Stream Processing with Apache Flink
 
Zio in real world
Zio in real worldZio in real world
Zio in real world
 
Mocking in Java with Mockito
Mocking in Java with MockitoMocking in Java with Mockito
Mocking in Java with Mockito
 

Similar to Implicit parameters, when to use them (or not)!

SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdfSummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
ARORACOCKERY2111
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overview
Yehuda Katz
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
Michael Peacock
 
前端MVC 豆瓣说
前端MVC 豆瓣说前端MVC 豆瓣说
前端MVC 豆瓣说
Ting Lv
 
Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the Finish
Yehuda Katz
 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)
Fabien Potencier
 
Dependency injection in Drupal 8
Dependency injection in Drupal 8Dependency injection in Drupal 8
Dependency injection in Drupal 8
Alexei Gorobets
 
Frameworks da nova Era PHP FuelPHP
Frameworks da nova Era PHP FuelPHPFrameworks da nova Era PHP FuelPHP
Frameworks da nova Era PHP FuelPHP
Dan Jesus
 

Similar to Implicit parameters, when to use them (or not)! (20)

SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdfSummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
 
Why Spring <3 Kotlin
Why Spring <3 KotlinWhy Spring <3 Kotlin
Why Spring <3 Kotlin
 
Using WordPress as your application stack
Using WordPress as your application stackUsing WordPress as your application stack
Using WordPress as your application stack
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overview
 
Enhancing Productivity and Insight A Tour of JDK Tools Progress Beyond Java 17
Enhancing Productivity and Insight  A Tour of JDK Tools Progress Beyond Java 17Enhancing Productivity and Insight  A Tour of JDK Tools Progress Beyond Java 17
Enhancing Productivity and Insight A Tour of JDK Tools Progress Beyond Java 17
 
How to actually use promises - Jakob Mattsson, FishBrain
How to actually use promises - Jakob Mattsson, FishBrainHow to actually use promises - Jakob Mattsson, FishBrain
How to actually use promises - Jakob Mattsson, FishBrain
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
 
Multilingualism makes better programmers
Multilingualism makes better programmersMultilingualism makes better programmers
Multilingualism makes better programmers
 
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
 
前端MVC 豆瓣说
前端MVC 豆瓣说前端MVC 豆瓣说
前端MVC 豆瓣说
 
Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the Finish
 
Grails 1.2 探検隊 -新たな聖杯をもとめて・・・-
Grails 1.2 探検隊 -新たな聖杯をもとめて・・・-Grails 1.2 探検隊 -新たな聖杯をもとめて・・・-
Grails 1.2 探検隊 -新たな聖杯をもとめて・・・-
 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)
 
Symfony2 from the Trenches
Symfony2 from the TrenchesSymfony2 from the Trenches
Symfony2 from the Trenches
 
Dependency injection in Drupal 8
Dependency injection in Drupal 8Dependency injection in Drupal 8
Dependency injection in Drupal 8
 
Singletons in PHP - Why they are bad and how you can eliminate them from your...
Singletons in PHP - Why they are bad and how you can eliminate them from your...Singletons in PHP - Why they are bad and how you can eliminate them from your...
Singletons in PHP - Why they are bad and how you can eliminate them from your...
 
¿Cómo de sexy puede hacer Backbone mi código?
¿Cómo de sexy puede hacer Backbone mi código?¿Cómo de sexy puede hacer Backbone mi código?
¿Cómo de sexy puede hacer Backbone mi código?
 
Frameworks da nova Era PHP FuelPHP
Frameworks da nova Era PHP FuelPHPFrameworks da nova Era PHP FuelPHP
Frameworks da nova Era PHP FuelPHP
 

Recently uploaded

Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo DiehlFuture Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Peter Udo Diehl
 

Recently uploaded (20)

Salesforce Adoption – Metrics, Methods, and Motivation, Antone Kom
Salesforce Adoption – Metrics, Methods, and Motivation, Antone KomSalesforce Adoption – Metrics, Methods, and Motivation, Antone Kom
Salesforce Adoption – Metrics, Methods, and Motivation, Antone Kom
 
UiPath Test Automation using UiPath Test Suite series, part 2
UiPath Test Automation using UiPath Test Suite series, part 2UiPath Test Automation using UiPath Test Suite series, part 2
UiPath Test Automation using UiPath Test Suite series, part 2
 
Optimizing NoSQL Performance Through Observability
Optimizing NoSQL Performance Through ObservabilityOptimizing NoSQL Performance Through Observability
Optimizing NoSQL Performance Through Observability
 
"Impact of front-end architecture on development cost", Viktor Turskyi
"Impact of front-end architecture on development cost", Viktor Turskyi"Impact of front-end architecture on development cost", Viktor Turskyi
"Impact of front-end architecture on development cost", Viktor Turskyi
 
10 Differences between Sales Cloud and CPQ, Blanka Doktorová
10 Differences between Sales Cloud and CPQ, Blanka Doktorová10 Differences between Sales Cloud and CPQ, Blanka Doktorová
10 Differences between Sales Cloud and CPQ, Blanka Doktorová
 
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
 
Behind the Scenes From the Manager's Chair: Decoding the Secrets of Successfu...
Behind the Scenes From the Manager's Chair: Decoding the Secrets of Successfu...Behind the Scenes From the Manager's Chair: Decoding the Secrets of Successfu...
Behind the Scenes From the Manager's Chair: Decoding the Secrets of Successfu...
 
Demystifying gRPC in .Net by John Staveley
Demystifying gRPC in .Net by John StaveleyDemystifying gRPC in .Net by John Staveley
Demystifying gRPC in .Net by John Staveley
 
Assuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyesAssuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyes
 
Measures in SQL (a talk at SF Distributed Systems meetup, 2024-05-22)
Measures in SQL (a talk at SF Distributed Systems meetup, 2024-05-22)Measures in SQL (a talk at SF Distributed Systems meetup, 2024-05-22)
Measures in SQL (a talk at SF Distributed Systems meetup, 2024-05-22)
 
Connector Corner: Automate dynamic content and events by pushing a button
Connector Corner: Automate dynamic content and events by pushing a buttonConnector Corner: Automate dynamic content and events by pushing a button
Connector Corner: Automate dynamic content and events by pushing a button
 
How world-class product teams are winning in the AI era by CEO and Founder, P...
How world-class product teams are winning in the AI era by CEO and Founder, P...How world-class product teams are winning in the AI era by CEO and Founder, P...
How world-class product teams are winning in the AI era by CEO and Founder, P...
 
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo DiehlFuture Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
 
UiPath Test Automation using UiPath Test Suite series, part 1
UiPath Test Automation using UiPath Test Suite series, part 1UiPath Test Automation using UiPath Test Suite series, part 1
UiPath Test Automation using UiPath Test Suite series, part 1
 
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
 
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
 
SOQL 201 for Admins & Developers: Slice & Dice Your Org’s Data With Aggregate...
SOQL 201 for Admins & Developers: Slice & Dice Your Org’s Data With Aggregate...SOQL 201 for Admins & Developers: Slice & Dice Your Org’s Data With Aggregate...
SOQL 201 for Admins & Developers: Slice & Dice Your Org’s Data With Aggregate...
 
IOS-PENTESTING-BEGINNERS-PRACTICAL-GUIDE-.pptx
IOS-PENTESTING-BEGINNERS-PRACTICAL-GUIDE-.pptxIOS-PENTESTING-BEGINNERS-PRACTICAL-GUIDE-.pptx
IOS-PENTESTING-BEGINNERS-PRACTICAL-GUIDE-.pptx
 
IoT Analytics Company Presentation May 2024
IoT Analytics Company Presentation May 2024IoT Analytics Company Presentation May 2024
IoT Analytics Company Presentation May 2024
 
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...
 

Implicit parameters, when to use them (or not)!

  • 1. 1
  • 2. Julien Truffaut Backend Scala developer for 10+ years FP instructor at fp-tower.com Co-founder of scalajobs.com 2
  • 3. Implicit parameters def newBlogPost(title: String)(implicit author: User): BlogPost class UserService(db: Connection)(implicit ec: ExecutionContext) {} 3
  • 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
  • 5. Parameters definition Explicit def fullName(firstName: String, lastName: String): String = s"$firstName $lastName" fullName("John", "Doe") // res0: String = "John Doe" 5
  • 6. Parameters definition Explicit def fullName(firstName: String, lastName: String): String = s"$firstName $lastName" fullName("John", "Doe") // res0: String = "John Doe" fullName(lastName = "Smith", firstName = "Bob") // res1: String = "Bob Smith" 6
  • 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
  • 9. Parameters definition Default value def fullName(firstName: String, lastName: String, title: Option[AcademicTitle] = None): String = { val suffix = title.map(" " + _.shortForm).getOrElse("") s"${firstName} ${lastName}${suffix}" } fullName("John", "Doe") // res: String = "John Doe" fullName("Bob", "Smith", Some(MasterOfScience)) // res: String = "Bob Smith MSc" 9
  • 10. Parameters definition Default value def kafkaListener( topic : String, bootstrapServers : String, commitTimeout : FiniteDuration = 50.millis, pollInterval : FiniteDuration = 15.millis, autoOffsetReset : AutoOffsetReset = AutoOffsetReset.Latest, ) 10
  • 11. Parameters definition Default value def kafkaListener( topic : String, bootstrapServers : String, commitTimeout : FiniteDuration = 50.millis, pollInterval : FiniteDuration = 15.millis, autoOffsetReset : AutoOffsetReset = AutoOffsetReset.Latest, ) Similar options: Overloads or Builder Pattern 11
  • 12. Parameters definition Implicit case class BlogPost( author : UserId, title : String, content : String, ) case class UserId(value: String) 12
  • 13. Parameters definition Implicit def createEmptyBlogPost(title: String)(implicit requesterId: UserId): BlogPost = BlogPost( author = requesterId, title = title, content = "" ) 13
  • 14. Parameters definition Implicit def createEmptyBlogPost(title: String)(implicit requesterId: UserId): BlogPost = BlogPost( author = requesterId, title = title, content = "" ) createEmptyBlogPost("Scala Implicits: The complete guide")(UserId("john_1234")) // Explicit call // res: BlogPost = BlogPost( // author = UserId("john_1234"), // title = "Scala Implicits: The complete guide", // content = "", // ) 14
  • 15. Parameters definition Implicit def createEmptyBlogPost(title: String)(implicit requesterId: UserId): BlogPost = BlogPost( author = requesterId, title = title, content = "" ) createEmptyBlogPost("Scala Implicits: The complete guide") // Implicit call // res: BlogPost = BlogPost( // author = UserId("john_1234"), // title = "Scala Implicits: The complete guide", // content = "", // ) 15
  • 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
  • 36. Environment Pattern case class BlogPost( author : UserId, title : String, content : String, createAt: Instant, ) 36
  • 37. Environment Pattern case class BlogPost( author : UserId, title : String, content : String, createAt: Instant, ) def createEmptyBlogPost(title: String)(implicit requesterId: UserId): BlogPost = BlogPost( author = requesterId, title = title, content = "", createAt = Instant.now(), ) 37
  • 38. Environment Pattern test("create blog post") { implicit val requesterId: UserId = UserId("john_1234") val result = createEmptyBlogPost("Test") assert(result == BlogPost(requesterId, "Test", "", ???)) } 38
  • 39. Environment Pattern trait Clock { def now(): Instant } 39
  • 40. Environment Pattern trait Clock { def now(): Instant } object Clock { val real: Clock = new Clock { def now(): Instant = Instant.now() } def static(timestamp: Instant): Clock = new Clock { def now(): Instant = timestamp } } 40
  • 41. Environment Pattern trait Clock { def now(): Instant } object Clock { val real: Clock = new Clock { def now(): Instant = Instant.now() } def static(timestamp: Instant): Clock = new Clock { def now(): Instant = timestamp } } 41
  • 42. Environment Pattern def createEmptyBlogPost(title: String)(implicit requesterId: UserId, clock: Clock): BlogPost = BlogPost( author = requesterId, title = title, content = "", createAt = clock.now(), ) 42
  • 43. Environment Pattern def createEmptyBlogPost(title: String)(implicit requesterId: UserId, clock: Clock): BlogPost = BlogPost( author = requesterId, title = title, content = "", createAt = clock.now(), ) object Main extends App { implicit val clock: Clock = Clock.real ... } class BlogPostTest extends AnyFunSuite { implicit val clock: Clock = Clock.static(Instant.EPOCH) ... } 43
  • 44. Environment Pattern object Main extends App { implicit val ec: ExecutionContext = ExecutionContext.global ... } class BlogDatabaseTest extends AnyFunSuite { implicit val ec: ExecutionContext = fixedSizeExecutionContext(1) ... } 44
  • 45. Environment parameters are Static in a given Context 45
  • 46. Context: Per request RequesterId TraceId Context: Prod / Test Clock ExecutionContext (for Future) RandomGenerator 46
  • 47. Advice 1. Use precise types def createQueue[A](implicit size: Int): Queue[A] = ... def connect(hostname: String)(implicit port: Int): Unit = ... 47
  • 48. Advice 1. Use precise types def createEmptyBlogPost(title: String)(implicit requesterId: UserId): BlogPost = BlogPost( author = requesterId, title = title, content = "", ) def shareBlog(blog: BlogPost, targetUser: UserId)(implicit requesterId: UserId): Future[Unit] = ... 48
  • 49. Advice 1. Use precise types case class RequesterId(userId: UserId) def createEmptyBlogPost(title: String)(implicit requesterId: RequesterId): BlogPost = BlogPost( author = requesterId.userId, title = title, content = "", ) 49
  • 50. Advice 2. Use clear and simple Context 50
  • 51. The value of implicit parameters must be Obvious 51
  • 52. Environment Pattern Alternatives Reader def createBlogPost(title: String) : Reader[(RequesterId, Clock), BlogPost] = ... def createProject(projectName: String): Reader[(RequesterId, Clock), Project] = ... for { blog <- createBlogPost("Implicits for the noob") project <- createProject("tutorial") } yield ... 52
  • 53. Environment Pattern Alternatives Reader def createBlogPost(title: String) : Reader[(RequesterId, Clock), BlogPost] = ... def createProject(projectName: String): Reader[RequesterId , Project] = ... for { blog <- createBlogPost("Implicits for the noob") project <- createProject("tutorial") ❌ } yield ... 53
  • 56. Typeclass Pattern val user : User = ... val userIds: List[UserId] = ... Ok(user) // res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"}) Ok(userIds) // res: HttpResponse(200, ["1234", "9464", "0582"]) 56
  • 57. Typeclass Pattern val user : User = ... val userIds: List[UserId] = ... Ok(user) // res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"}) Ok(userIds) // res: HttpResponse(200, ["1234", "9464", "0582"]) def Ok[A](value: A): HttpResponse = HttpResponse(200, value.toJson) 57
  • 58. Typeclass Pattern val user : User = ... val userIds: List[UserId] = ... Ok(user) // res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"}) Ok(userIds) // res: HttpResponse(200, ["1234", "9464", "0582"]) def Ok[A](value: A): HttpResponse = HttpResponse(200, value.toJson) 58
  • 59. Typeclass Pattern val user : User = ... val userIds: List[UserId] = ... Ok(user) // res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"}) Ok(userIds) // res: HttpResponse(200, ["1234", "9464", "0582"]) def Ok[A](value: A): HttpResponse = HttpResponse(200, value.toJson) Ok((x: Int) => x + 1) // ??? 59
  • 60. Typeclass Pattern trait JsonEncoder[A] { def encode(value: A): Json } 60
  • 61. Typeclass Pattern trait JsonEncoder[A] { def encode(value: A): Json } def Ok[A](value: A, encoder: JsonEncoder[A]): HttpResponse = HttpResponse(200, encoder.encode(value)) 61
  • 62. Typeclass Pattern trait JsonEncoder[A] { def encode(value: A): Json } def Ok[A](value: A, encoder: JsonEncoder[A]): HttpResponse = HttpResponse(200, encoder.encode(value)) val userEncoder : JsonEncoder[User] = ... val listUserIdsEncoder: JsonEncoder[List[UserId]] = ... Ok(user, userEncoder) // res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"}) Ok(userIds, listUserIdsEncoder) // res: HttpResponse(200, ["1234", "9464", "0582"]) 62
  • 63. Typeclass Pattern trait JsonEncoder[A] { def encode(value: A): Json } def Ok[A](value: A)(implicit encoder: JsonEncoder[A]): HttpResponse = HttpResponse(200, encoder.encode(value)) implicit val userEncoder : JsonEncoder[User] = ... implicit val listUserIdsEncoder: JsonEncoder[List[UserId]] = ... Ok(user) // res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"}) Ok(userIds) // res: HttpResponse(200, ["1234", "9464", "0582"]) 63
  • 64. Typeclass Pattern object JsonSerializer { implicit val userEncoder : JsonEncoder[User] = ... implicit val listUserIdsEncoder: JsonEncoder[List[UserId]] = ... } import JsonSerializer._ Ok(user) // res: HttpResponse(200, {"userId" : "1234", "name" : "John Doe"}) 64
  • 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
  • 73. Typeclass Pattern trait JsonEncoder[A] { def encode(value: A): Json } 73
  • 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
  • 75. Typeclass Pattern Use typeclass derivation to automatically create instances object JsonEncoder { implicit def list[A](implicit valueEncoder: A): JsonEncoder[List[A]] = ... } 75
  • 76. Typeclass Pattern Use typeclass derivation to automatically create instances object JsonEncoder { // shapeless, magnolia implicit def caseClass[A: JsonEncoder, B: JsonEncoder]: JsonEncoder[CaseClass[A, B]] = ... } case class User(userId: UserId, email: String) object User { implicit val encoder: JsonEncoder[User] = JsonEncoder.caseClass } 76
  • 77. Typeclass Pattern (Scala 3) Use typeclass derivation to automatically create instances case class User(userId: UserId, email: String) derives JsonEncoder 77
  • 78. Advice Don't define instances in custom objects object Client1Encoder { implicit val userEncoder: JsonEncoder[User] = ... } object Client2Encoder { implicit val userEncoder: JsonEncoder[User] = ... } import Client1Encoder._ // lots of code Ok(user) 78
  • 79. The value of implicit parameters must be Obvious 79
  • 80. Ordering trait Ordering[A] { def compare(left: A, right: A): Int } 80
  • 81. Ordering trait Ordering[A] { def compare(left: A, right: A): Int } trait List[A] { def sorted(implicit ordering: Ordering[A]): List[A] = ... } 81
  • 82. Ordering trait Ordering[A] { def compare(left: A, right: A): Int } trait List[A] { def sorted(implicit ordering: Ordering[A]): List[A] = ... } List(8,1,9).sorted // ??? 82
  • 83. Ordering trait Ordering[A] { def compare(left: A, right: A): Int } trait List[A] { def sorted(implicit ordering: Ordering[A]): List[A] = ... } List(8,1,9).sorted // res3: List[Int] = List(1, 8, 9) 83
  • 84. Custom Ordering implicit val descendingIntOrdering: Ordering[Int] = Ordering.Int.reverse List(8,1,9).sorted // res4: List[Int] = List(9, 8, 1) 84
  • 85. Custom Ordering implicit val descendingIntOrdering: Ordering[Int] = Ordering.Int.reverse List(8,1,9).sorted // res4: List[Int] = List(9, 8, 1) List(8,1,9).sorted(Ordering.Int.reverse) // res5: List[Int] = List(9, 8, 1) 85
  • 86. Better Ordering trait List[A] { def sorted(ordering: Ordering[A]): List[A] = ... } List(8,1,9).sorted(Ordering.Int.ascending) // res: List[Int] = List(1, 8, 9) List(8,1,9).sorted(Ordering.Int.descending) // res: List[Int] = List(9, 8, 1) 86
  • 87. Review Explicit Simple Great tooling Default value When one value is much more common than the others Implicit When some parameters value are static within a context When writing generic functions 87
  • 88. The value of implicit parameters must be Obvious 88
  • 89. 89
  • 91. 91