DEV Community

Avalander
Avalander

Posted on

Implicit classes in Scala

In Scala there are two things (as far as I know) that can be declared with the implicit keyword: classes and function parameters. As the word implies, the keyword implicit signals the compiler to help you a bit so that you can avoid some clutter in your code.

Implicit classes

With implicit classes we can add extra functionality to existing types in an easy and convenient way. Let's imagine that we want to extend the String class with a method that will prepend another string only if the first one is not empty.

implicit class ExtString(x: String) {
  def prependIfNotEmpty(prefix: String): String =
    if (x.isEmpty) x
    else prefix ++ x
}

And now we can assume that all instances of String in the scope have the method prependIfNotEmpty.

"type=unicorn&name=Twiligth Sparkle".prependIfNotEmpty("?") // "?type=unicorn&name=Twiligth Sparkle"
"".prependIfNotEmpty("Lorem ipsum") // ""

Wait, isn't that monkey patching?

Well yes, but actually no

Monkey patching is a pattern used (and abused) in dynamic languages to override or add custom behaviour to existing types. To achieve a similar effect in Javascript, we could monkey patch the String prototype.

String.prototype.prependIfNotEmpty = function (prefix) {
  return this.length > 0
    ? prefix + this.valueOf()
    : this.valueOf()
}

So, what's the difference?

The difference is that in Javascript we are, in fact, modifying the original String prototype, which means that the change will affect the entire application. Thus, if we are using two modules or libraries that patch the same method in the same object with different behaviour, it's not going to work as we expect.

Scala is not actually touching the String type at all. When we add an implicit class, the compiler is going to expand our usages of <string>.prependIfNotEmpty to this:

new ExtString(<string>).prependIfNotEmpty(<prefix>)

And that's why our ExtString class receives a String argument, if you were wondering.

Moreover, the compiler will only use our ExtString if it is in scope, so we need to either declare it in the same scope where we are using it or import it, and a compile error will be thrown if there are two or more possible options with the same specificity to expand an implicit type in the same scope. So if we would declare a ExtString2 class with the same method in the same scope, the compiler would complain that there are two options to expand <string>.prependIfNotEmpty. This makes unexpected behaviour issues due to type patching virtually impossible in Scala.

Bonus: prependIfNotEmpty operator

Another cool thing about Scala is that you can use non-alphanumeric characters in method names, and that you can invoke a method with one argument with object method argument instead of object.method(argument). Therefore, implementing operators for a type is as trivial as defining a method.

Instead of defining the method prependIfNotEmpty, we could create an operator for that. We can use the same notation as the prepend operator for arrays: +:, maybe throw in a question mark to signal that the operation will only happen if the string is not empty: +?:

implicit class ExtString(x: String) {
  def +?: (prefix: String): String =
    if (x.isEmpty) x
    else prefix ++ x
}

"?" +?: "123" // "?123"
"?" +?: ""     // ""

Note that in Scala operators that end with : associate to the right, which means that we write it in the form of argument operator object instead of object operator argument.

There are different opinions regarding whether adding operators is a good idea or not. It is not immediately obvious that +?: means prepend only if the string is not empty. I'm saying that you can use custom operators, not that you should.

Top comments (2)

Collapse
 
sirseanofloxley profile image
Sean Allin Newell

So are Scala implicits like .NET extension methods?

Extension methods enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Extension methods are a special kind of static method, but they are called as if they were instance methods on the extended type.

Collapse
 
avalander profile image
Avalander

I know next to nothing about .NET, but after a quick read it seems to be the same principle, yeah.