Open Credo

May 25, 2016 | Software Consultancy

Complex Kotlin

In this post I’m going to demonstrate the implementation of Complex, a Kotlin class handling complex numbers, which uses operator overloading to provide the usual arithmetic operations for those numbers. In the process, I’ll also demonstrate a Kotlin pattern which I call “complicit conversion”, and show how to implement complicit conversion between two types: Double and Complex.

WRITTEN BY

Dominic Fox

Dominic Fox

Complex Kotlin

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 Doubles 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 Doubles 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.

RETURN TO BLOG

SHARE

Twitter LinkedIn Facebook Email

SIMILAR POSTS

Blog