Kotlin
A concise multiplatform language developed by JetBrains
M6.2 Available
Today we release Kotlin M6.2 which brings some interesting and important features. The newly released IntelliJ IDEA 13 is now supported too. Let’s take a look.
Language Enhancements
There are some long awaited and important changes to the language.
Tail Call Optimization
Good news to all fans of functional programming: Kotlin now supports tail call optimization (TCO). This means that we can now write code in functional style without the risk of blowing up the call stack. For example, here’s what a left fold on an iterator may look like:
tailRecursive fun Iterator.foldl(acc : A, f : (e : T, acc : A) -> A) : A = if (!hasNext()) acc else foldl(f(next(), acc), f)
Note that the function is annotated with [tailRecursive]: this is mandatory when you want tail calls to be optimized. We do not perform TCO silently, for two reasons:
- Debugging. Since tail-call-optimized functions do not make actual recursive calls, debuggers won’t show any stack frames representing recursion, you won’t be able to inspect local variables on previous levels of recursion etc. Thus we believe the least surprising way of coming about this is make it explicit in the code.
- Safety. We all know a funny thing about code: it often does not do what it appears to be doing ;) Same with tail calls: often times a call looks as if it were a tail call, but in fact it isn’t. This means that no optimization can be made, and performance would be compromised. With [tailRecursive] annotation the compiler knows where calls must be optimizable and warns you when you make a mistake.
We hope functional-oriented libraries like funKTionale will benefit from this feature, as well as Kotlin’s standard library.
Thanks to Sergey Mashkov for the contributions in making this happen.
Constant Expressions
As you know, Kotlin is very strict on types: even numeric types are not coerced to each other implicitly. This is necessary both to implement our approach of having all types defined in the same way (as classes) and to avoid strange artifacts that Java presents sometimes. Fortunately, we can eliminate most of the little inconveniences this brings by being smart with constant expressions. Now this work is finalized, and you can do things like this:
val x: Long = -1
or
val b: Byte = -1
In fact, what happens under the hood is that the compiler computes the value of the constant expression on the right, checks that it fits the range of the expected type (Long or Byte) and converts the constant. The same does not happen with non-constants because they can not be checked.
Also, the compiler warns you when there is an arithmetic overflow:
val monthInMilliseconds = 1000 * 60 * 60 * 24 * 30 // WARNING
Here the result exceed the range of type Int, and the compiler warns you about it. You can fix the overflow by making the first number Long:
val monthInMillisecondsL = 1000.toLong() * 60 * 60 * 24 * 30 // OK
But this yes, well, too long. We now support the familiar “L” suffix to the numbers:
val monthInMilliseconds = 1000L * 60 * 60 * 24 * 30 // OK
Note that the “l” suffix for Longs, notorious for its looking so much like “1” in many fonts, is not allowed.
Similarly, there’s the “F” suffix for floats. Since little “f” does not look like any digit and altogether looks more pleasant, it is not forbidden. :)
The story above is all good for integral types, but floating-point values are tricky, as we all know. For example:
println(0.1 < 0.1f) // prints "true"
It has a lot to do with the binary nature of these decimal-looking numbers, and the cases where conversions are lossless do not look intuitive to humans. Thus, we do not convert floating-point values automatically. Double is used by default, if you want a float, say so explicitly:
val fl: Float = 1.0f
Java Interoperability
Mutability Annotations
You remember that collections in Kotlin have read-only interfaces. What about Java libraries that expose collections? Along with well-known @Nullable/@NotNull, Kotlin now provides annotations to mark Java collections @ReadOnly or @Mutable.
Given the following Java code
public List<EMail> activeEmails() { ... }
when calling it from Kotlin, the return value is List<EMail> and Kotlin treats this as a MutableList. We can now decorate the method with a @ReadOnly annotation, so that the list can not be mutated through this reference:
@ReadOnly public List<EMail> activeEmails() { ... }
which means that we can no longer call the add() on it for instance
We can do the same when it comes to parameters. Take the following Java method
void processEmails(List<EMail> emails) { ... }
in Kotlin, this would be represented as
fun processEmails(emails: List<EMail>)
which can be turned into
fun processEmails(emails: MutableList<EMail>)
by annotating the original Java code with @Mutable
void processEmails(@Mutable List<EMail> emails) { ... }
By the way, these annotations provide excellent documentation for your Java code, too.
Kotlin’s knowledge of nullable types available to Java
The compiler now emits @Nullable and @NotNull annotations in the Java byte code. This allows IntelliJ IDEA to make use of these and warn of nullability violations when calling Kotlin code from Java.
For instance
class Customer { fun checkSocialSecurity(accountNumber: String) { ... } }
when called from Java
Customer customer = new Customer(); customer.checkSocialSecurity(null); // WARNING
would cause a warning.
Android SDK
We now provide annotations for the entire Android SDK, making it even easier and safer to work on Android projects.
Java to Kotlin Converter
Last but not least, and while not entirely related to Java Interop, we have also improved the Java to Kotlin converter, providing cleaner code and nicer formatting.
JavaScript Improvements
We’re continuing to improve the JavaScript compiler support. You can now configure whether you want sourcemaps generated and where you’d want these placed via the Kotlin Compiler page in Settings | Preferences
Additionally you can specify output file prefix and postfixes for the generated Kotlin JavaScript code.
We’ve also fixed a couple of JavaScript related issues including
- Default arguments to functions are now fixed
- super-constructor calls are supported
- Class objects in traits are supported
Ant Support Improvements
Java Compiler
Importing tasks using typeDef instead of taskDef gives us access to not only the existing<kotlinc> task but also the new <withKotlin/> task for Ant which allows the compilation of Kotlin files when using the Java compiler task <javac>, using the src attributes defined in the latter.
JavaScript Compiler
You can also compile Kotlin to JavaScript using Ant. There is a new task <kotlin2js> which given src and output attributes, will compile Kotlin to JavaScript. A variety of additional options are also available, similar to those in the IDE for enabling sourcemaps, prefix and suffix of generated files, etc.
IDE Enhancements
We can now provide IntelliJ IDEA with additional command line parameters to the compiler as well as whether we want warnings to be generated
In addition, some other IDE features have been enhanced/updated:
- Smart Completion: While only started, we are working towards providing smarter completion in Kotlin, along the lines of what we already provide for Java.
- Find Usages: Improved performance. Ability to find local and private declarations, support for value/type parameters and property getters/setters, along with some bug fixes.
- Change Signature Refactoring: Now supports function hierarchies.
- Rename Refactoring: Fixes issues when renaming Kotlin classes and functions from Java code as well as renaming Kotlin base functions with overrides.
While we’ve only covered some of the improvements with this release, there have been an additional 120 issues resolved including features and bug fixes, since Milestone 6.1.
Download
If you are using IntelliJ 13, you already have Kotlin M6.2 available out of the box. Otherwise, you can download the Compiler from the release page and the plugin from the IntelliJ IDEA repository.