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?
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)
So are Scala implicits like .NET extension methods?
I know next to nothing about .NET, but after a quick read it seems to be the same principle, yeah.