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) |
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) |
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") | |
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
- In same conjure
- Imported instance
- companion object
- 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.