Say goodbye to implicits
Magda Stożek
Contextual abstractions in Scala 3
Agenda
- About implicits
- Problems in Scala 2
- Use cases - Scala 2 vs Scala 3
- Migration
Implicits make us powerful
- providing context
- adding functionality
- type classes
- implicit conversions
- dependency injection
- expressing capabilities
- computing new types
- proving relationships between types
Implicits make us miserable
- implicit parameter
- implicit argument
- implicit value
- implicit object
- implicit def
- implicit class
- implicit import incantations
- sneaky conversions
Scala 3
Scala 2
- Providing context
- Adding functionality
- Type classes
- Conversions
- Imports
- Unused names
- Compiler errors
vs
Scala 3
1. Providing context
- implicit vals
implicit val ec: ExecutionContext = ExecutionContext.global
Future {
def apply[T](body: => T)(implicit executor: ExecutionContext): Future[T] = {...}
}
Scala 2
val myFuture: Future[Int] = Future {
println("Finding the answer...")
42
}
?
?
1. Providing context
- using/given
val myFuture: Future[Int] = Future {
println("Finding the answer...")
42
}
Scala 3
given ec: ExecutionContext = ExecutionContext.global
Future {
def apply[T](body: => T)(using executor: ExecutionContext): Future[T] = {...}
}
?
?
2. Adding functionality
object other_package {
case class Book(title: String, pages: Int)
}
val book = Book("Winnie-the-Pooh", 160)
myBook.qualifiesForChallenge //true
val book = Book("Too short", 5)
myBook.qualifiesForChallenge //false
implicit class Qualifier(b: Book) {
def qualifiesForChallenge: Boolean = b.pages > 20
}
?
- implicit classes
Scala 2
2. Adding functionality
- extension methods
object other_package {
case class Book(title: String, pages: Int)
}
val book = Book("Winnie-the-Pooh", 160)
myBook.qualifiesForChallenge //true
val book = Book("Too short", 5)
myBook.qualifiesForChallenge //false
?
extension (b: Book)
def qualifiesForChallenge = b.pages > 20
Scala 3
3. Type classes
trait Scorable[T] {
def score(t: T): Int
}
val book = Book("Dune", 600)
val initialScore = CurrentScore(0).addItem(book) // CurrentScore(600)
val article = Article("Understanding type classes")
val newScore = initialScore.addItem(article) // CurrentScore(601)
?
- implicit val/object/def (with context bounds and implicitly)
implicit val scorableBook = new Scorable[Book] {
override def score(b: Book): Int = b.pages
}
implicit val scorableArticle = new Scorable[Article] {
override def score(t: Article): Int = 1
}
case class CurrentScore(value: Int) {
def addItem[T: Scorable](item: T): CurrentScore =
CurrentScore(value + implicitly[Scorable[T]].score(item))
}
Scala 2
?
3. Type classes
trait Scorable[T] {
def score(t: T): Int
}
val book = Book("Dune", 600)
val initialScore = CurrentScore(0).addItem(book) // CurrentScore(600)
val article = Article("Understanding type classes")
val newScore = initialScore.addItem(article) // CurrentScore(601)
- implicit val/object/def (with implicit parameters)
implicit val scorableBook = new Scorable[Book] {
override def score(b: Book): Int = b.pages
}
implicit val scorableArticle = new Scorable[Article] {
override def score(t: Article): Int = 1
}
case class CurrentScore(value: Int) {
def addItem[T](item: T)(implicit scoring: Scorable[T]): CurrentScore =
CurrentScore(value + scoring.score(item))
}
Scala 2
3. Type classes
- given instances (with context bounds and summon)
?
given Scorable[Book] with {
override def score(b: Book): Int = b.pages
}
given Scorable[Article] with {
override def score(t: Article): Int = 1
}
case class CurrentScore(value: Int) {
def addItem[T: Scorable](item: T): CurrentScore =
CurrentScore(value + summon[Scorable[T]].score(item))
}
trait Scorable[T] {
def score(t: T): Int
}
Scala 3
val book = Book("Dune", 600)
val initialScore = CurrentScore(0).addItem(book) // CurrentScore(600)
val article = Article("Understanding type classes")
val newScore = initialScore.addItem(article) // CurrentScore(601)
3. Type classes
- given instances (with using)
given Scorable[Book] with {
override def score(b: Book): Int = b.pages
}
given Scorable[Article] with {
override def score(t: Article): Int = 1
}
case class CurrentScore(value: Int) {
def addItem[T](item: T)(using scoring: Scorable[T]): CurrentScore =
CurrentScore(value + scoring.score(item))
}
trait Scorable[T] {
def score(t: T): Int
}
val book = Book("Dune", 600)
val initialScore = CurrentScore(0).addItem(book) // CurrentScore(600)
val article = Article("Understanding type classes")
val newScore = initialScore.addItem(article) // CurrentScore(601)
Scala 3
4. Conversions
- implicit def
case class Item(value: String) extends AnyVal
def print(item: Item): Unit = println(s"*** ${item.value} ***")
val book = Book("Dune", 600)
print(book)
?
implicit def bookToItem(book: Book): Item = Item(book.title)
Scala 2
4. Conversions
- Conversion class
case class Item(value: String) extends AnyVal
def print(item: Item): Unit = println(s"*** ${item.value} ***")
val book = Book("Dune", 600)
print(book)
?
given Conversion[Book, Item] with
def apply(book: Book): Item = Item(book.title)
Scala 3
5. Imports
- importing implicits
import scala.concurrent.ExecutionContext.Implicits.*
val future = Future(42) //OK
Scala 2
5. Imports
- importing givens
import scala.concurrent.ExecutionContext.Implicits.*
val future = Future(42) //error
import scala.concurrent.ExecutionContext.Implicits.given
val future = Future(42) //OK
Scala 3
import scala.concurrent.ExecutionContext.Implicits.{given, *}
val future = Future(42) //OK
6. Unused names
trait SeqOps (...) {
def sorted[B](implicit ord: Ordering[B]): Seq = ...
}
- mandatory names
implicit val byTitle: Ordering[Book] = (x: Book, y: Book) => x.title.compareTo(y.title)
val myBooks = Seq(Book("Dune", 600), Book("Winnie-the-Pooh", 160))
myBooks.sorted
?
Scala 2
6. Unused names
trait SeqOps (...) {
def sorted[B](using ord: Ordering[B]): Seq = ...
}
- anonymous givens
given byTitle: Ordering[Book] = (x: Book, y: Book) => x.title.compareTo(y.title)
val myBooks = Seq(Book("Dune", 600), Book("Winnie-the-Pooh", 160))
myBooks.sorted
given Ordering[Book] = (x: Book, y: Book) => x.title.compareTo(y.title)
?
?
Scala 3
7. Compiler errors
could not find implicit value for evidence parameter of type
org.example.TypeClassesFullContextBoundsExample.Scorable[org.example.Domain.Book]
Scala 2
7. Compiler errors
The following import might fix the problem:
import concurrent.ExecutionContext.Implicits.global
no implicit argument of type
org.example.TypeClassesFullExample.Scorable[org.example.domain.Book] was found
for parameter scoring of method addItem in class CurrentScore
Scala 3
Migration
- interoperability
- deprecation?
Summary
- intention, not mechanism
- given/using
- extension methods
- Conversion class
More
Thank you
given Finished[Presentation] with {
override def ask(q: Question): Unit = q.ask()
}
10% off:
Magda10
Say goodbye to implicits
By Magda Stożek
Say goodbye to implicits
- 1,615