May 25, 2016 | Software Consultancy
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
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.
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.
Agile India 2022 – Systems Thinking for Happy Staff and Elated Customers
Watch Simon Copsey’s talk from the Agile India Conference on “Systems Thinking for Happy Staff and Elated Customers.”Lean-Agile Delivery & Coaching Network and Digital Transformation Meetup
Watch Simon Copsey’s talk from the Lean-Agile Delivery & Coaching Network and Digital Transformation Meetup on “Seeing Clearly in Complexity” where he explores the Current…When Your Product Teams Should Aim to be Inefficient – Part 2
Many businesses advocate for efficiency, but this is not always the right goal. In part one of this article, we explored how product teams can…