Implicit in Scala

In Scala, Implicit is very important. But it is very ambiguous and hard to understand. And it is learning curve to enter Scala world. This post is basic about Scala Implicit.

Implicit is operated by the compiler. If there is some error, for example, Instance type is not compatible or need implicit parameter Compiler is looking for Implicit conversion or Implicit Instance. And this post is about making implicit Conversion or Instance.

Implicit Conversion

implicit def

We can convert some instance to other instance automatically by using implicit. First, we can use ‘Implicit Conversion’. Let’s say that, there are some instances like that.

trait Printable {
def print: String
}
case class PrintableClass(value: String) extends Printable {
override def print: String = s"value: ${value.toString}"
}
case class ValueCaseClass(value: String)
view raw Instance.sc hosted with ❤ by GitHub

If we want to convert ‘ValueCaseClass’ to ‘PrintableClass’, you can use by using implicit def like this.

implicit def convertValueCaseClassToPrintable(valueCaseClass: ValueCaseClass): PrintableClass =
PrintableClass(valueCaseClass.value)
val printableClass: PrintableClass = ValueCaseClass("Implicit Def") // by using convertValueCaseClassToPrintable
println(printableClass.print)
view raw ImplicitDef.sc hosted with ❤ by GitHub

In this code,


val printableClass1: PrintableClass = ValueCaseClass("Implicit Def")

printableClass1’s type is PrintableClass but we set ValueCaseClass. It is compile error. But, Compile is looking for implicit conversion and finally find implicit definition ‘convertValueCaseClassToPrintable’ and get PrintableClass.

implicit class

You can also convert by using implicit class.

implicit class ConvertableValueCaseClass(valueCaseClass: ValueCaseClass) {
def toPrintable: PrintableClass = PrintableClass(valueCaseClass.value)
}
val printableClass2: PrintableClass = ValueCaseClass("Implicit class").toPrintable // by using implicit class
println(printableClass2.print)

In this code,


val printableClass2: PrintableClass = ValueCaseClass("Implicit class").toPrintable

ValueCaseClass don’t has toPrintable. Compiler looking for implicit class and find ‘ConvertableValueCaseClass’ and there is ‘toPrintable’.

Implicit Instance

In Scala, def can get implicit parameter.

def print(implicit printableClass: PrintableClass): Unit = {
println(printableClass.print)
}
implicit val printableClass = PrintableClass("Implicit Val")
print
view raw ImplicitVal.sc hosted with ❤ by GitHub

In code above, ‘print’ need ‘printableClass’ parameter. Compiler automatically pass implicit instance ‘printableClass’.

Implicit Order

Above, there we can get the implicit instance. And I think it is very useful. We can get Implicit Instance in many ways. Let see an example.

object ImplicitOrder extends App{
implicit val implicitIntInstance1: Instance[String] = Instance("Implicit Instance in Same Conjure")
def getString(implicit implicitInstance: Instance[String]): String = implicitInstance.value
import Imported.implicitIntInstance1
println(getString)
}
case class Instance[T](value: T)
object Instance {
implicit val implicitIntInstance1: Instance[String] = Instance("Implicit Instance in Companion Object")
}
object Imported {
implicit val implicitIntInstance1: Instance[String] = Instance("Implicit Instance in imported")
}

Can you guess which string is printed in the console? The answer is ‘Implicit Instance in Same Conjure’ Implicit order is

  1. In same conjure
  2. Imported instance
  3. companion object
  4. default imported

So, if we delete

def getString(implicit implicitInstance: Instance[String]): String = implicitInstance.value

, “Implicit Instance in imported” will be printed. And if we delete

import Imported.implicitIntInstance1

, “Implicit Instance in Companion Object” will be printed

Default implicit instance by companion object.

Let’s pay attention to the companion object implicit instance. We can make default instance by implicit instance in companion object.

case class Hello(value: String)
trait Printer[T] {
def print(value: T): String
}
object Printer {
implicit val IntPrinter: Printer[Int] = new Printer[Int] {
override def print(value: Int): String = s"Type: Int - $value"
}
implicit val PersonPrinter: Printer[Hello] = new Printer[Hello] {
override def print(value: Hello): String = s"Type: Hello - $value"
}
}
def print[T](value: T)(implicit printer: Printer[T]): String = printer.print(value)
val int: Int = 1
val hello: Hello = Hello("ktz")
print(int) // Type: Int - 1
print(hello) // Type: Hello - Hello(ktz)
object InjectedObject {
implicit val injectedIntPrinter: Printer[Int] = new Printer[Int] {
override def print(value: Int): String = s"Injected - Type: Int - $value"
}
}
import InjectedObject.injectedIntPrinter
print(int) // Injected - Type: Int - 1

Above code


print(int) // Type: Int - 1
print(hello) // Type: Hello - Hello(ktz)

Function ‘print’ is passed default implicit instance in companion object


implicit val IntPrinter: Printer[Int] = new Printer[Int] {
  override def print(value: Int): String = s"Type: Int - $value"
}

implicit val PersonPrinter: Printer[Hello] = new Printer[Hello] {
  override def print(value: Hello): String = s"Type: Hello - $value"
}

But next ‘print(int)’ is passed ‘injectedIntPrinter’. Like this, we can inject any instance by importing some instance.

Advertisement