Explicit and implicit conversion
Explicit conversion is when we have a value of type A
, and a function or method that converts values of type A
to type B
. For example, suppose we have a Java Date
, but what we need is a Java 8 Instant
. We can explicitly convert a Date
to an Instant
via the toInstant
method on the Date
class. In Java, it’s common to use overloaded methods to perform explicit conversion between types, so that the “same” method can accept values of both types:
// Method that accepts Dates, converts them and delegates to method accepting Instants.
public String getFormatted(Date date) {
return getFormatted(date.toInstant()); // explicit conversion
}
// Method which accepts Instants, and provides actual implementation of formatting behaviour.
public String getFormatted(Instant instant) {
return instant.atOffset(ZoneOffset.UTC).toLocalDateTime()
.format(DateTimeFormatter.ISO_DATE_TIME);
}
In Scala, we can define an implicit conversion between Date
and Instant
, so that whenever that conversion is in scope a Date
can be passed directly to any function that expects an Instant
, and the compiler will perform the conversion automatically:
implicit def date2instant(date: java.util.Date): java.time.Instant = date.toInstant
def getFormatted(instant: java.time.Instant): String =
instant.atOffset(ZoneOffset.UTC).toLocalDateTime
.format(DateTimeFormatter.ISO_DATE_TIME)
val date = new java.util.Date
val formatted = getFormatted(date)
This is a powerful language feature, but ad hoc use of it can make code harder to follow, as the reader must keep track of which implicit conversions are in scope.
Complicit conversion
Kotlin does not support implicit conversions, but we can use a combination of extension methods and operator overloading to implement conversions that come into play when values are combined. Let’s take the classic motivating example for operator overloading: a Complex
class, which represents complex numbers which can be added, subtracted, multiplied and divided:
data class Complex(val real: Double, val imaginary: Double) {
companion object {
val zero = Complex(0.0, 0.0)
}
fun reciprocal(): Complex {
val scale = (real * real) + (imaginary * imaginary)
return Complex(real / scale, -imaginary / scale)
}
fun abs(): Double = Math.hypot(real, imaginary)
operator fun unaryMinus(): Complex = Complex(-real, -imaginary)
operator fun plus(other: Double): Complex = Complex(real + other, imaginary)
operator fun minus(other: Double): Complex = Complex(real - other, imaginary)
operator fun times(other: Double): Complex = Complex(real * other, imaginary * other)
operator fun div(other: Double): Complex = Complex(real / other, imaginary / other)
operator fun plus(other: Complex): Complex =
Complex(real + other.real, imaginary + other.imaginary)
operator fun minus(other: Complex): Complex =
Complex(real - other.real, imaginary - other.imaginary)
operator fun times(other: Complex): Complex =
Complex(
(real * other.real) - (imaginary * other.imaginary),
(real * other.imaginary) + (imaginary * other.real))
operator fun div(other: Complex): Complex = this * other.reciprocal()
}
Thanks to the operator
functions defined on this class, we can combine Complex
numbers using the standard arithmetic operators:
fun mandelbrot(c: Complex, maxIterations: Int): Int? {
tailrec fun iterate(z: Complex, iterations: Int): Int? =
when {
iterations == maxIterations -> null
(z.abs() > 2.0) -> iterations
else -> iterate((z * z) + c, iterations + 1)
}
return iterate(Complex.zero, 0)
}
// prints an ASCII Mandelbrot set
for (i in -40.0..40.0) {
for (r in -40.0..40.0) {
print(mandelbrot(Complex(r - 25.0, i) / 35.0, 256)
?.let { 'a' + (it % 26) }
?: ' '
)
}
println()
}
Which outputs the following very fetching ASCII Mandelbrot set:
bbbbbbbbcccccccccccccddddddddddddddddddddddddddddddeeeeeeeffffeeeeeddddddddcccccc
bbbbbbbccccccccccccddddddddddddddddddddddddddddddeeeeeeefiihffffeeeeeddddddddcccc
bbbbbbbcccccccccccdddddddddddddddddddddddddddddeeeeeeeeffginhgfffeeeeeedddddddccc
bbbbbbccccccccccdddddddddddddddddddddddddddddeeeeeeeeefffgiojggggfeeeeeeedddddddc
bbbbbccccccccccddddddddddddddddddddddddddddeeeeeeeeeefffgghpnihhihfeeeeeeeddddddd
bbbbbccccccccdddddddddddddddddddddddddddddeeeeeeeeeeffffgghjnkjkq gfeeeeeeedddddd
bbbbccccccccddddddddddddddddddddddddddddeeeeeeeeeeefffffghijlxpnjhgfeeeeeeeeddddd
bbbbcccccccddddddddddddddddddddddddddddeeeeeeeeeeeeffffgghijlpnkihgffeeeeeeeedddd
bbbcccccccddddddddddddddddddddddddddddeeeeeeeeeeeefffffghiiknvplihggfffeeeeeeeddd
bbbccccccdddddddddddddddddddddddddddeeeeeeeeeeeeefffffghjkkog wwjihgffffeeeeeeedd
bbccccccdddddddddddddddddddddddddddeeeeeeeeeeeeeffffgghmnbnrb lolqrgfffffeeeeeeed
bbcccccdddddddddddddddddddddddddddeeeeeeeeeeeefffffgghimolw mpwkhgffffffeeeeed
bcccccdddddddddddddddddddddddddddeeeeeeeeeeeeffffgggghijme slhhgffffffeeeee
bccccdddddddddddddddddddddddddddeeeeeeeeeeeeffggggghhhijuz fpkihgggffffffeee
bcccddddddddddddddddddddddddddeeeeeeeeeeeefffggggghhhiikof xkihhggggfffffee
ccccdddddddddddddddddddddddddeeeeeeeeeeefffghhhhhhhhijjkn okjiihggggggggfe
cccdddddddddddddddddddddddddeeeeeeeeeeffffgrnjlbjiikllkmnrw tnlkjkmihhghhjjgf
ccdddddddddddddddddddddddddeeeeeeeeefffffghitrdnkjkowqtyyuzo avspumvgkiihhiklhf
ccddddddddddddddddddddddddeeeeeeefffffffgghjpytoknmp teb ukfmjjkkkmoig
cdddddddddddddddddddddddeeeeeeeffffffffggghjmp toa hpmlpyupsph
cddddddddddddddddddddddeeeeeffffffffffggghhjki x rpg khplg
dddddddddddddddddddddeeeeefffffffffffgggghijkyt y slig
ddddddddddddddddddddeeefffffffffffffgggghijklotf akhg
ddddddddddddddddddeeefffffffffffffggggghiksooct vmjhg
dddddddddddddddeeeeefggggffffffffggggghijuegu sljhh
dddddddddddddeeeeefgniggggggggggggghhhhijpz jpkih
ddddddddddeeeeeeffggirihhhhhhhhhhhhhhhiikmsk wljj
dddddddeeeeeeeefffggirjihhhiiojihhhhhiijmud sio
ddddeeeeeeeeefffffggjuljjkjjloljjiiiiijkt l j
ddeeeeeeeeeefffffgghilyvoqmlmqtlmtjjjjklqm tm
deeeeeeeeeeffffffgghijlrwetoov tqnlkkkkot mj
eeeeeeeeeefffffffghhijkoh fw q mz sollmoz nk
eeeeeeeeefffffffghhhijlqe ifunnqu q
eeeeeeeeffffffffghhillnsc fppt fg
eeeeeeefffffffghiiijlsqb rw mi
eeeeeefffffggghjmjjjlod vb lh
eeeeeffggggghhijxllmnt qjh
eeeefgggggghhhijllqurw qhg
ggglhhhgghhhhijknu ib sihg
ghhkliiirjijjrlnai mjihg
jpljhhg
ghhkliiirjijjrlnai mjihg
ggglhhhgghhhhijknu ib sihg
eeeefgggggghhhijllqurw qhg
eeeeeffggggghhijxllmnt qjh
eeeeeefffffggghjmjjjlod vb lh
eeeeeeefffffffghiiijlsqb rw mi
eeeeeeeeffffffffghhillnsc fppt fg
eeeeeeeeefffffffghhhijlqe ifunnqu q
eeeeeeeeeefffffffghhijkoh fw q mz sollmoz nk
deeeeeeeeeeffffffgghijlrwetoov tqnlkkkkot mj
ddeeeeeeeeeefffffgghilyvoqmlmqtlmtjjjjklqm tm
ddddeeeeeeeeefffffggjuljjkjjloljjiiiiijkt l j
dddddddeeeeeeeefffggirjihhhiiojihhhhhiijmud sio
ddddddddddeeeeeeffggirihhhhhhhhhhhhhhhiikmsk wljj
dddddddddddddeeeeefgniggggggggggggghhhhijpz jpkih
dddddddddddddddeeeeefggggffffffffggggghijuegu sljhh
ddddddddddddddddddeeefffffffffffffggggghiksooct vmjhg
ddddddddddddddddddddeeefffffffffffffgggghijklotf akhg
dddddddddddddddddddddeeeeefffffffffffgggghijkyt y slig
cddddddddddddddddddddddeeeeeffffffffffggghhjki x rpg khplg
cdddddddddddddddddddddddeeeeeeeffffffffggghjmp toa hpmlpyupsph
ccddddddddddddddddddddddddeeeeeeefffffffgghjpytoknmp teb ukfmjjkkkmoig
ccdddddddddddddddddddddddddeeeeeeeeefffffghitrdnkjkowqtyyuzo avspumvgkiihhiklhf
cccdddddddddddddddddddddddddeeeeeeeeeeffffgrnjlbjiikllkmnrw tnlkjkmihhghhjjgf
ccccdddddddddddddddddddddddddeeeeeeeeeeefffghhhhhhhhijjkn okjiihggggggggfe
bcccddddddddddddddddddddddddddeeeeeeeeeeeefffggggghhhiikof xkihhggggfffffee
bccccdddddddddddddddddddddddddddeeeeeeeeeeeeffggggghhhijuz fpkihgggffffffeee
bcccccdddddddddddddddddddddddddddeeeeeeeeeeeeffffgggghijme slhhgffffffeeeee
bbcccccdddddddddddddddddddddddddddeeeeeeeeeeeefffffgghimolw mpwkhgffffffeeeeed
bbccccccdddddddddddddddddddddddddddeeeeeeeeeeeeeffffgghmnbnrb lolqrgfffffeeeeeeed
bbbccccccdddddddddddddddddddddddddddeeeeeeeeeeeeefffffghjkkog wwjihgffffeeeeeeedd
bbbcccccccddddddddddddddddddddddddddddeeeeeeeeeeeefffffghiiknvplihggfffeeeeeeeddd
bbbbcccccccddddddddddddddddddddddddddddeeeeeeeeeeeeffffgghijlpnkihgffeeeeeeeedddd
bbbbccccccccddddddddddddddddddddddddddddeeeeeeeeeeefffffghijlxpnjhgfeeeeeeeeddddd
bbbbbccccccccdddddddddddddddddddddddddddddeeeeeeeeeeffffgghjnkjkq gfeeeeeeedddddd
bbbbbccccccccccddddddddddddddddddddddddddddeeeeeeeeeefffgghpnihhihfeeeeeeeddddddd
bbbbbbccccccccccdddddddddddddddddddddddddddddeeeeeeeeefffgiojggggfeeeeeeedddddddc
bbbbbbbcccccccccccdddddddddddddddddddddddddddddeeeeeeeeffginhgfffeeeeeedddddddccc
bbbbbbbccccccccccccddddddddddddddddddddddddddddddeeeeeeefiihffffeeeeeddddddddcccc
bbbbbbbbcccccccccccccddddddddddddddddddddddddddddddeeeeeeeffffeeeeeddddddddcccccc
Because we also have overloaded versions of the operator functions which take Double
values, When instances of Complex
are arithmetically combined with Double
s the results are always Complex
values:
// prints Complex(real=2.0, imaginary=2.0)
println(Complex(1.0, 2.0) + 1.0)
By adding extension methods to Double
, we can make it so that Double
s behave like Complex
numbers when Complex
numbers are combined with them:
operator fun Double.plus(other: Complex): Complex = other + this
operator fun Double.minus(other: Complex): Complex = -other + this
operator fun Double.times(other: Complex): Complex = other * this
operator fun Double.div(other: Complex): Complex = other.reciprocal() * this
This gives us what I call “complicit conversion” between Double
and Complex
: where you would use a Complex
number, in an expression combining Complex
numbers using arithmetic operators, you can always use a Double
instead:
// prints Complex(real=3.6153846153846154, imaginary=1.0769230769230769)
println(2.0 + Complex(1.0, 2.0) - (8.0 / Complex(4.0, 6.0)))
For the case where we want to convert a Double
directly to a Complex
, we can add an explicit conversion via an extension method:
fun Double.complex(): Complex = Complex(this, 0.0)
// prints Complex(real=0.5, imaginary=-0.0)
println(2.0.complex().reciprocal())
This blog is written exclusively by the OpenCredo team. We do not accept external contributions.