Complex Numbers in Scala
Overview
I recently delivered an introductory talk about Scala at an internal geek’s event at SAP. In this talk, I used an example complex numbers class to illustrate important language concepts and features. In many respects, this is a classical example that can be found in many other introductory materials about Scala, for example in this Scala Tutorial for Java Programmers. Nevertheless, I thought it’s a wonderful example worthy of another try. During the talk, I started with a very simple one-liner and gradually added more capabilities to it, while at the same time introducing the language features that made them possible. I ended up with a more or less complete and usable complex numbers implementation in just a few lines of code, which nevertheless allowed things that
would not be possible with other languages (Java), such as operator arithmetics, seamless conversion between complex and real numbers, and “free” equality and comparison. In this post, I would like to reproduce this part of my talk. If you are interested in Scala, but haven’t mastered the language yet, this can be a good introduction to the conciseness and power of this remarkable programming language.
Starting Point
Our starting point is quite simple:
class Complex(val re: Double, val im: Double)
The single line above is the entire class definition. It has two Double
fields, which are public (as this is the default in Scala) and immutable (due to the val
keyword). The above line also defines implicitly a default two-argument constructor, so that Complex
instances can already be created and initialised. Let’s do this in the Scala interpreter:
scala> val x = new Complex(1, 2) x: Complex = Complex@3997ca20
If you compare this class definition to the code that would be needed to achieve the same in Java, it becomes evident that Scala is much more concise and elegant here, letting you express your intent clearly in the fewest possible lines of code.
Overriding Methods
The default string representation of Complex
above is rather unfriendly. It would have been much better if it contained the class members in a format suitable for a complex number. To achieve this, we will of course override the toString
method which our class inherits from Any
, the root of the Scala class hierarchy.
class Complex(val re: Double, val im: Double) { override def toString = re + (if (im < 0) "-" + -im else "+" + im) + "*i" }
Note that the override
keyword is mandatory in Scala. It has to be used when you override something, otherwise you get a complier error. This is one of the many ways Scala helps you as a programmer to avoid silly mistakes, in this case accidental overrides. Now, if you create a Complex
instance in the interpreter, you will get:
scala> val x = new Complex(1, 2) x: Complex = 1.0+2.0*i
Adding Methods and Operators
Since complex numbers are numbers, one thing we would like to be able to do with them are arithmetic operations such as addition. One way to achieve this would be to define a new add
method:
class Complex(val re: Double, val im: Double) { def add(c: Complex) = new Complex(re + c.re, im + c.im) ... }
With the above definition, we can add complex numbers by invoking our new method using the familiar notation:
scala> val x = new Complex(1, 2) x: Complex = 1.0+2.0*i scala> val y = new Complex(3, 4) y: Complex = 3.0+4.0*i scala> x.add(y) res0: Complex = 4.0+6.0*i
In Scala, we could also invoke our method, as well as in fact any method, using an operator notation, with the same result:
scala> x add y res1: Complex = 4.0+6.0*i
And since we have operator notation, we could as well call our method +
, and not add
. Yes, this is possible in Scala.
class Complex(val re: Double, val im: Double) { def +(c: Complex) = new Complex(re + c.re, im + c.im) ... }
Now, adding x and y can be expressed simply as:
scala> x + y res2: Complex = 4.0+6.0*i
If you are familiar with languages like C++, this may seem a lot like operator overloading. But in fact, it is not really correct to say that Scala has operator overloading. Instead, Scala doesn’t really have operators at all. Every operator-looking construct, including arithmetic operations on simple types, is in fact a method call. This is of course much more consistent and easier to use than traditional operator overloading, which treats operators as a special case. In the final version of our Complex
class, we will add the operator methods -, *, and / for the other arithmetic operations.
Overloading Constructors and Methods
Complex numbers with a zero imaginary part are in fact real numbers, and so real numbers can be seen simply as a special type of complex numbers. Therefore it should be possible to seamlessly convert between these two kinds of numbers and mix them in arithmetic expressions. To achieve this in our example class, we will overload the existing constructor and +
method so that they accept Double
instead of Complex
:
class Complex(val re: Double, val im: Double) { def this(re: Double) = this(re, 0) ... def +(d: Double) = new Complex(re + d, im) ... }
Now, we can create Complex
instances by specifying just their real parts, and add real numbers to them:
scala> val y = new Complex(2) y: Complex = 2.0+0.0*i scala> y + 2 res3: Complex = 4.0+0.0*i
Constructor and method overloading in Scala is similar to what can be found in Java and other languages. Constructor overloading is somewhat more restrictive, however. To ensure consistency and help avoid common errors, every overloaded constructor has to call the default constructor in its first statement, and only the default constructor is allowed to call a superclass constructor.
Implicit Conversions
If instead of y + 2
above we execute 2 + y
we will get an error, since none of the Scala simple types has a method +
accepting Complex
as an argument. To improve the situation, we can define an implicit conversion from Double
to Complex
:
implicit def fromDouble(d: Double) = new Complex(d)
With this conversion in place, adding a Complex
instance to a double becomes possible:
scala> 2 + y res3: Complex = 4.0+0.0*i
Implicit conversions are a powerful mechanism to make incompatible types interoperate seamlessly with each other. It almost renders other similar features such as method overloading obsolete. In fact, with the above conversion, we don’t need to overload the +
method anymore. There are indeed strong reasons to prefer implicit conversions to method overloading, as explained in Why Method Overloading Sucks in Scala. In the final version of our Complex
class, we will add implicit conversions from the other simple types as well.
Access Modifiers
As a true object-oriented language, Scala offers powerful access control features which can help you ensure proper encapsulation. Among them are the familiar private
and protected
access modifiers which you can use on fields and methods to restrict their visibility. In our Complex
class, we could use a private field to hold the absolute value, or modulus of a complex number:
class Complex(val re: Double, val im: Double) { private val modulus = sqrt(pow(re, 2) + pow(im, 2)) ... }
Trying to access modulus
from the outside will of course result in an error.
Unary Operators
To allow clients to get the modulus of a Complex
instance, we will add a new method that returns it. Since modulus is a very common operation, it would be nice to be able to invoke it again as an operator. However, this has to be a unary operator this time. Fortunately, Scala helpfully allows us to define this kind of operators as well:
class Complex(val re: Double, val im: Double) { private val modulus = sqrt(pow(re, 2) + pow(im, 2)) ... def unary_! = modulus ... }
Methods starting with unary_
can be invoked as unary operators:
scala> val y = new Complex(3, 4) y: Complex = 3.0+4.0*i scala> !y res2: Double = 5.0
In the final version of our Complex
class, we will add unary operators for the +
and -
signs and for the complex conjugate.
Companion Objects
Besides traditional classes, Scala also allows defining objects with the object
keyword, which essentially defines a singleton class and its single instance at the same time. If an object has the same name as a class defined in the same source file, it becomes a companion object of that class. Companion objects have a special relationship to the classes they accompany, in particular they can access private methods and fields of that class.
Scala has no static
keyword, because the language creators felt that it contradicts true object orientation. Therefore, companion objects in Scala are the place to put members that you would define as static in other languages, for example constants, factory methods, and implicit conversions. Let’s define the following companion object for our Complex
class:
object Complex { val i = new Complex(0, 1) def apply(re: Double, im: Double) = new Complex(re, im) def apply(re: Double) = new Complex(re) implicit def fromDouble(d: Double) = new Complex(d) }
Our companion object has the following members:
i
is a constant for the imaginary unit- The two
apply
methods are factory methods which allow creatingComplex
instances by invokingComplex(...)
instead of the less convenientnew Complex(...)
. - The implicit conversion
fromDouble
is the one introduced above.
With the companion object in place, we can now write expressions such as:
scala> 2 + i + Complex(1, 2) res3: Complex = 3.0+3.0*i
Traits
Strictly speaking, complex numbers are not comparable to each other. Nevertheless, for practical purposes it would be useful to introduce a natural ordering based on their modulus. We would like of course to be able to compare complex numbers with the same operators <, <=, >, and >= that are used to compare other numeric types.
One way to achieve this would be to define all these 4 methods. However, this would introduce some boilerplate as the methods <=, >, and >= will of course all call the < method. In Scala, this can be avoided by using the powerful feature known as traits.
Traits are similar to interfaces in Java, since they are used to define object types by specifying the signature of the supported methods. Unlike Java, Scala allows traits to be partially implemented, so it is possible to define default implementations for some methods, similarly to Java 8 default methods. In Scala, a class can extend, or mix-in multiple traits due to mixin class composition.
For our example, we will mix-in the Ordered
trait into our Complex
class. This trait provides implementations of all 4 comparison operators, which all call the abstract method compare
. Therefore, to get all comparison operations “for free” all we need to do is provide a concrete implementation of this method.
class Complex(val re: Double, val im: Double) extends Ordered[Complex] { ... def compare(that: Complex) = !this compare !that ... }
Now, we can compare complex numbers as desired:
scala> Complex(1, 2) > Complex(3, 4) res4: Boolean = false scala> Complex(1, 2) < Complex(3, 4) res5: Boolean = true
Case Classes and Pattern Matching
Interestingly, comparing Complex
instances for equality still doesn’t work as expected:
scala> Complex(1, 2) == Complex(1, 2) res6: Boolean = false
This is because the ==
method invokes the equals
method, which implements reference equality by default. One way to fix this would be to override the equals
method appropriately for our class. Of course, overriding equals
means overriding hashCode
as well. Although that would be rather trivial, it would add an unwelcome bit of boilerplate.
In Scala, we can skip all this if we define our class as a case class by adding the keyword case
. This adds automatically several useful capabilities, among them the following:
- adequate
equals
andhashCode
implementations - a companion object with an
apply
factory method - class parameters are implicitly defined as
val
case class Complex(re: Double, im: Double) ... }
Now, comparing for equality works as expected:
scala> i == Complex(0, 1) res6: Boolean = true
But the most important capability of case classes is that they can be used in pattern matching, another unique and powerful Scala feature. To illustrate it, let’s consider the following toString
implementation:
override def toString() = this match { case Complex.i => "i" case Complex(re, 0) => re.toString case Complex(0, im) => im.toString + "*i" case _ => asString } private def asString = re + (if (im < 0) "-" + -im else "+" + im) + "*i"
The above code matches this
against several patterns representing the constant i
, a real number, a pure imaginary number, and everything else. Although it could be written without pattern matching as well, this way is shorter and easier to understand. Pattern matching becomes really invaluable if you need to process complex object trees, as it provides a much more elegant and concise alternative to the Visitor design pattern typically used in such cases.
Wrap-up
The final version of our Complex
class looks as follows:
import scala.math._ case class Complex(re: Double, im: Double) extends Ordered[Complex] { private val modulus = sqrt(pow(re, 2) + pow(im, 2)) // Constructors def this(re: Double) = this(re, 0) // Unary operators def unary_+ = this def unary_- = new Complex(-re, -im) def unary_~ = new Complex(re, -im) // conjugate def unary_! = modulus // Comparison def compare(that: Complex) = !this compare !that // Arithmetic operations def +(c: Complex) = new Complex(re + c.re, im + c.im) def -(c: Complex) = this + -c def *(c: Complex) = new Complex(re * c.re - im * c.im, im * c.re + re * c.im) def /(c: Complex) = { require(c.re != 0 || c.im != 0) val d = pow(c.re, 2) + pow(c.im, 2) new Complex((re * c.re + im * c.im) / d, (im * c.re - re * c.im) / d) } // String representation override def toString() = this match { case Complex.i => "i" case Complex(re, 0) => re.toString case Complex(0, im) => im.toString + "*i" case _ => asString } private def asString = re + (if (im < 0) "-" + -im else "+" + im) + "*i" } object Complex { // Constants val i = new Complex(0, 1) // Factory methods def apply(re: Double) = new Complex(re) // Implicit conversions implicit def fromDouble(d: Double) = new Complex(d) implicit def fromFloat(f: Float) = new Complex(f) implicit def fromLong(l: Long) = new Complex(l) implicit def fromInt(i: Int) = new Complex(i) implicit def fromShort(s: Short) = new Complex(s) } import Complex._
With this remarkably short and elegant implementation we can do all the things described above, and a few more:
- create instances with
Complex(...)
- get the modulus with
!x
and the conjugate with~x
- perform arithmetic operations with the usual operators +, -, *, and /
- mix complex, real, and integer numbers freely in arithmetic expressions
- compare for equality with == and !=
- compare modulus-based with <, <=, >, and >=
- get the most natural string representation
If you are inclined for some experimentation, I would encourage you to paste the above code in the Scala interpreter (using :paste
first) and play around with these capabilities to get a better feeling.
Conclusion
Scala is considered by many to be a rather complex language. Perhaps this is why it’s so suitable for complex numbers … Puns aside, where some people see complexity I see unmatched elegance and power. I hope that this post illustrated this nicely. I am myself still learning Scala and far from being an expert. Are you aware of better ways to implement the above capabilities? I would love to hear about that.
Reference: Complex Numbers in Scala from our JCG partner Stoyan Rachev at the Stoyan Rachev’s Blog blog.
Nice introduction to Scala concepts. Looking at the final shape of your class, there are two things: (1) Implicit conversions. You should only include the conversion from `Double`. Scala already provides an implicit mechanism called numeric widening which makes the implicit functions for `Short`, `Int`, `Float`, and even `Long` (something that can be argued about) superfluous. (2) Avoiding overloading is usually a good thing. I don’t see any need for your secondary constructor (`def this(re: Double)`). Furthermore, employing default arguments you can also leave out the overloaded `apply` method in the companion object. Just use ` case class Complex(re: Double,… Read more »
Interesting and fun little exercise. I have a couple of nits.
As someone already pointed out, a default argument in the constructor is preferable to an alternate constructor.
In the modulus calculation, you have
sqrt(pow(re, 2) + pow(im, 2))
I think just using sqrt(re * re + im * im) is cleaner and possibly more efficient. The “pow” function may take logarithms, which is unnecessary.
You don’t need the “new” in your + operator.
Is it standard to compare by modulus? I was not aware of that.
Thanks for your comments. You are right abut pow and new. No, comparing by modulus is not standard. In fact, there is no standard comparison for complex numbers. I picked this way of comparing for the sake of example only.