Hello,
I’ve been evaluating Kotlin for a couple of months now. I’ve been really enjoying it so far, but I do have some concerns that are maybe worth mentioning. I’m no language design expert, but I’ve ported a decently sized codebase to Kotlin as part of the evaluation, so I have a good understanding of its look and feel.
But before that, I’d like to first list a few things that are really good in Kotlin right now. My initital motivation for trying Kotlin was that I was starting a new project and needed a more productive language than Java. I required Java interop, great IDE support, no serious compromises in terms of performance and something that was not as crazy complicated as Scala. Kotlin qualified and I’ve been very happy with the following features:
- var/val and compile time null-safety.
val lets me get rid of the final keyword once and for all. Fields, method arguments and local variables should have been implicitly final from day one in Java (with maybe a “mutable” keyword for enabling mutability). Kotlin’s null-safety also makes nullability annotations obsolete. Not having to ever write “@NotNull final” makes me happy.
- Named arguments and default values.
No more method overloading! Especially with the recent fix to this, it’s amazing how much cleaner a lot of code becomes, class constructors getting the most benefit. Btw, I haven’t encountered a need for a secondary constructor yet, so +1 for not allowing them. If there’s ever a need for a 2ndary constructor, you should probably break up the class in two anyway.
- Static extension functions and extension function literals.
Simply awesome. Nothing to say here, both the design and implementation of this feature is brilliant. It will even become better when method inlining gets implemented.
There are lots of other goodies in Kotlin of course (string templates, infix notation, operator overloading), but these 3 are the top reasons that made me want to invest time in it. I understand a lot of features are still under discussion or already planned to be removed, but I do have I little comment on this: I don’t care for tuples or complex pattern matching, but I’d be interested to hear why “it” is getting dropped. It’s a cool little feature that JDK8 won’t have and I’ve kinda gotten used to it.
Now, the not-so-good part. It’s all Java-interop related:
- Null-safety issues.
The fact that anything that comes from Java is nullable is really annoying with certain APIs that are guaranteed to never return null. I’m talking about external libraries where @NotNull cannot be applied. I understand that Kotlin uses some kind of intrinsics to get around this problem with some very common Java APIs (see this), or other weird patterns like:
public inline fun String.trim() : String = (this as java.lang.String).trim().sure()
Obviously this is an annoyance to the Kotlin dev team as well and these kinds of solutions do not scale. We can't expect Kotlin to cover every useful non-null returning method call we could use. Some examples:
val a1 : Array<String> = "foo.bar".split("[.]") // OK
// JDK API, I wanted to implement a "split" extension method on CharSequence, similar to String.split.
val pat = Pattern.compile("[.]") // Pattern?, can't be null
val a2 = pat?.split("foo.bar") // Array<String?>?, can't be null, can't have null elements
// Work-around: cast to Array<String>, results in "This cast can never succeed" warning.
public inline fun CharSequence.split(pattern : Pattern) : Array<String> = pattern.split(this)!! as Array<String>
// Google Guava
val s = ImmutableSet.of(1, 2, 3) // ImmutableSet<Int>?, can't be null
Anyway, I'm sure everyone has encountered dozens of such examples that aren't covered by Kotlin intrinsics. I understand why this behaviour is in place and technically I agree with it. But in practice, it's simply too annoying and doesn't match the rest of the language's "sweetness" and JetBrains' "develop with pleasure" moto. There's nothing exciting about having an API call that the developer knows will never return null look like shit with a weird "!!" next to it. I can think of two way to improve this situation: a) Provide a way to annotate existing Java method signatures (including static methods) as non-null returning and b) Make all Java method return non-null implicitly, unless there's a @Nullable present. I personally prefer b).
- Lack of support for functional interfaces
This is an awesome feature from JDK8’s lambdas. It’s not necessary in Kotlin code, but Java interop becomes a pain again. Assuming a post-JDK8 environment, when most libraries will have lambda-style APIs, we’ll need a way to pass Kotlin function literals to Java methods as functional interfaces. Some code to illustrate the issue:
trait KotlinPredicate<T> {
fun test(t: T): Boolean;
}
// Work-around for Java call
public inline fun <T> java.lang.Iterable<T>.allMatch(predicate: (T) -> Boolean): Boolean {
return this.allMatch(object:Predicate<T> {
public override fun test(t: T?): Boolean {
return predicate(t!!)
}
})
}
class FunctionLiteralTest(val name: String) {
public fun testLiteral(predicate: (String) -> Boolean) {
println(predicate(name))
}
public fun testTrait(predicate: KotlinPredicate<String>) {
println(predicate.test(name))
}
public fun test() {
testLiteral() { it.startsWith(“foo”) }
testTrait(object:KotlinPredicate<String> {
override fun test(t: String): Boolean = t.startsWith(“foo”)
})
// This could work with functional interface support. Beneficial for Kotlin code too.
//testTrait() { it.startsWith(“foo”) }
val list = java.util.ArrayList<String>(10);
// Java call
list.allMatch(object : Predicate<String> {
public override fun test(t: String?): Boolean {
return t!!.startsWith(“foo”);
}
})
// Kotlin wrapper of the Java call
// This could work without the extension function above
// if Kotlin had support for functional interfaces.
list.allMatch() { it.startsWith(“foo”) }
}
}
With the current situation, we either have to use object expression or wrap in extension methods. Object expressions are simply too verbose for this use case and extension methods have both coding and performance overhead.
(a related example of what will be possible with Java’s method references: here)
- Kotlin collections do not match JDK collections.
This is painfully obvious with JDK8+lambdas. Ctrl+space on a collection and there’s so much stuff in there, from both the JDK library and the Kotlin library’s extension methods. It’s a mess. I understand that Kotlin works with different backends and Java is not the only language supported, but this situation requires clean-up. I hope this will be sorted out when JDK8 APIs are finalized.
Another related problem is the implementation differences. When pipelining operations on a collection, JDK8 has gone with lazy evaluation for everyting but the last operation (everything is implemented in terms of Iterable currently). Kotlin on the other hand prefers eager evaluation for operations performed directly on the collection, whereas lazy evaluation is available for Iterators. So, the following code performs very differently between Java and Kotlin (method parameters omitted):
// Java: source -> lazy -> lazy -> lazy -> eager
// Kotlin: source -> eager -> eager -> eager -> eager
collection.map().filter().map().reduce();
To match the Java behavior, Kotlin requires:
collection.iterator().map().filter().map().reduce();
Basically, Kotlin's way has an advantage when performing a single operation (in Java, you'd need collection.map().into()) and Java's way is beneficial with multiple operations. Imho, Kotlin should mimic Java's way, not only for consistency and to avoid surprises for Java developers, but because performing multiple operations is more common in practice.
- Non-final variable capture
Kotlin is able to wrap mutable variables in closures, using the jet.runtime.SharedVar helper class. Java’s lambdas do not allow mutable variables to be captured (unless they’re effectively final). A “trick” like SharedVar is of course possible in Java too, but I believe that the reasons for Java not doing it automatically are important and Kotlin should mimic that as well. See this, under “7. Variable capture” for a good explanation. It’s probably not a big issue for experienced developers, but with more and more libraries being multithreaded these days, I think mutable variable capture is a danger Kotlin can do without.
That’s it for now, thanks for reading.