Scala

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.Complex_number_illustration 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 creating Complex instances by invoking Complex(...) instead of the less convenient new 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 and hashCode 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.

Stoyan Rachev

Software developer, architect, and agile software engineering coach at SAP
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
reaction
reaction
11 years ago

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 »

Russ
Russ
10 years ago

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.

Stoyan
Stoyan
10 years ago
Reply to  Russ

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.

Back to top button