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())