Heiko's Blog

About programming and other fun things

We Are Reactive

Implicits Unchained – Type-safe Equality – Part 1

| Comments

Beside the fusion of object orientation and functional programming, I think that implicits – implicit classes, conversions, parameters and values – are Scala’s most intrinsic feature. They are a very powerful tool and therefore you should follow the Spider-Man principle: “with great power comes great responsibility”. But hey, they also say “no risk no fun”, so let’s fasten our seatbelts and start a discovery tour of implicitlandia.

Use case: type-safe equality

Our discovery tour will be centered around the use case of type-safe equality. In Java and – because of Java compatibility – also in Scala, equality is untyped:

1
2
3
4
abstract class Any {
  def equals(other: Any): Boolean = 
  
}

As you can see, equals takes an argument of type Any. Therefore we can compare apples with pies, a Person with an Option[Person], etc. In most cases, this is not what we want, and this can lead to errors which are very hard to analyze. Instead, we’d like to have a type-safe equality operation like:

1
2
3
123 === 123 // true
"a" === "b" // false
123 === "a" // Should not compile!

First approach

All right, what we need is a === method that can be called on objects of some type and takes another object of that particular type instead of Any. In other words, we need a polymorphic method:

1
def ===[A](a: A): Boolean = 

But there is no === on Any or any of the other standard classes. And Scala doesn’t support extension methods, right? Well, not directly, but we can use implicits to achieve the same effect.

The basic idea is quite simple: Wrap an object that needs to be extended with one that provides the “extension method” and define an implicit conversion from the type that is to be extended to the wrapper. In our case the type to be extended is any type, so we have to provide a polymorphic conversion. Since Scala 2.10 these two steps can be unified by providing an implicit class:

1
2
3
4
5
object SimpleEquality {
  implicit class Equal[A](left: A) {
    def ===(right: A): Boolean = left == right
  }
}

As implicit classes are desugared into the wrapper class and the implicit conversion by the Scala compiler, they can’t be defined at the top-level. Therefore we define the implicit Equal class inside of the SimpleEquality singleton object. By importing SimpleEquality._ we bring the implicit conversion into scope and can make use of our nice type-safe equality operation:

1
2
3
4
5
6
7
8
9
10
11
12
13
scala> import name.heikoseeberger.demoequality.SimpleEquality._
import name.heikoseeberger.demoequality.SimpleEquality._

scala> 123 === 123
res0: Boolean = true

scala> "a" === "b"
res1: Boolean = false

scala> 123 === "a"
<console>:11: error: type mismatch;
 found   : String("a")
 required: Int

Nice, eh? Well, some of you might be concerned about the potential performance impact of creating wrapper objects at runtime. Fortunately Scala 2.10 offers value classes, which we can use to avoid this creation of the wrappers. All we have to do is extend from AnyVal and make the wrapped value a val. Then the Scala compiler will inline the “extension methods”. Here is the final state of our first and simple approach:

1
2
3
4
5
object SimpleEquality {
  implicit class Equal[A](val left: A) extends AnyVal {
    def ===(right: A): Boolean = left == right
  }
}

Conclusion

In this post we have discussed the problems with untyped equality and shown how to provide a simple type-safe equality operation using implicit classes. In the next post we’ll look at the drawbacks of our simple solution and provide a more sophisticated one which makes use of more implicit goodness.

The full source code is available on GitHub.

Comments