Channels ▼
RSS

JVM Languages

Language of the Month: Kotlin


Higher-Order Functions

The isNotEmpty() example presented earlier is a handy but rather minor improvement over what the JDK already has. Let's do something real, and extend JDK's collections with map() and filter() and many other operations to make data processing easy.

Let's start with map(). It takes a collection and transforms every item in it according to some rule, yielding a collection of transformed results. For example, if I have a list of users and I want to get a list of their last names, I use the following call to map():

  users.map(object : Mapper<User, String> {
    fun transform(it : User) : String = it.lastName
  })

The object expression on the first line is Kotlin's counterpart of Java's anonymous inner class. But you don't have to write those clumsy anonymous classes to do simple things in Kotlin. What you actually write is:

users.map {it -> it.lastName}

This be familiar to Groovy users. The construct in curly braces is called a function literal. Some people prefer to call it a lambda (which is the Greek letter "& #923;" used by Alonzo Church to denote function literals in his calculus back in 1936) or a closure (which is a set of external variables such a literal actually uses). No matter what you call it, it denotes a function that is passed to map() as an argument. Map, therefore, is called a higher-order (in fact, second-order) function, because it is a function that takes another function as an argument. Putting theory aside, this is exactly what we do when using the Strategy pattern (for example, passing a Comparator to the sort() function), but in a more concise syntax.

Let's look at the definition of map():

fun <T, R> Collection<T>.map(f : (T) -> R) : List<R> {
  val result = ArrayList<R>(this.size())
  for (x in this) result.add(f(x))
  return result
}

This is a generic function that turns a collection of Ts into a list of Rs. It takes a function f of type (T)->R — that is, a function that turns a T into an R — and applies it to every element of the input collection. Note that f(), while being a parameter, can be called as a normal function inside map().

The complete syntax for the call would be

  users.map({(it : User) : String -> it.lastName})

We now have provided the compiler with all the information, including the parameter type and the return type. Fortunately, most of it can be inferred automatically, and all we really have to say is:

  users.map {it.lastName}

All the types are inferred, and even the parameter name it is chosen automatically. This should be familiar from Groovy, as well as the convention that the last function literal can be passed to a function without parentheses.

In addition to making collections and callbacks nicer, higher-order functions turn many boilerplate patterns into library functions and prevent code pollution. For example, this is how we use locks in Java:

myLock.lock();
try {
 // Do stuff
} fnally {
  myLock.unlock();
}

The funny thing about this piece of code is that the only relevant parts are the lock name and the "Do stuff" comment. All the rest is noise. In Kotlin, this noise can be hidden in the standard library:

fun <T> lock(l : Lock, body : () -> T) {
  l.lock()
  try {
   return body()
  } fnally {
    l.unlock()
  }
}

It is called this way:

lock(myLock) {
  // Do stuff
}

This doesn't just save LOCs — it prevents errors, too.

Java APIs and Nullability

One of the main goals for Kotlin it to be safer than Java. This means preventing common types of errors that arise repeatedly in Java programs. One of the most common examples of such errors is the NullPointerException, which is so annoying and thus mentioned so frequently, that it even has a short name: NPE.

Kotlin aims to eliminate NPEs from our programs. To achieve this, we introduce a concept of nullable types. A simple type in Kotlin, such as a String or List<Boolean>, cannot hold a null, so calling a function on an object of this type is always NPE-safe. If you need something to hold nulls occasionally, you need to declare its type as nullable, which is denoted by a question mark, e.g. String? or List<Boolean>?. You are not allowed to call methods on nullable references without taking precautions.

As an example, let's take the readLine() function, which returns a null when no more input is available. Trying to determine the number of characters returned by readLine() may lead to an NPE:

val input = readLine()
if (input.length < 5) {
  println("At least 5 characters required")
}

The Kotlin compiler won't let you do this because you must first check if the result is not null:

if (input != null && input.length < 5) {
  println("At least 5 characters required")
}

The compiler is smart enough to see that you have checked for null and handle the value accordingly.

Sometimes you just don't care if something is null or not. In this case, you can use a safe call operator "?.," which will be familiar to Groovy users:

if (input?.charAt(0) != ‘a') {
  println("Expecting a string that starts with ‘a'")
}

The safe call operator works as follows: If the receiver is not null, the call is performed as usual; otherwise, nothing happens and the whole expression evaluates to null.

With Java libraries, you can't know if something may return null or not unless you can understand JavaDocs, which our compiler is unlikely to do. A safe strategy is to assume that everything that comes from Java (apart from primitive types) can be null. This works well for many cases, but sometimes you just know that a certain API never returns a null. In this case, there are two ways out. First, you can call the sure() function that turns a nullable reference into a non-null one (and throws an NPE if called on a null):

  input.sure().length < 5

Here you're saying, "I understand what I'm doing, I'm sure I want an NPE if a null comes in." Note, we are considering replacing the sure() function with a "!!" operator to make it even more alarming.

The second option is nicer: You can annotate your Java code so that Kotlin knows that it does not return nulls. The good thing about this is that you can do it on demand and use external annotations in places where you cannot touch the source code. Most common libraries, such as JDK collections, come pre-annotated with Kotlin. We are planning to infer these annotations automatically in the spirit of IntellJ IDEA's "infer nullity" action.

The Road Ahead

You can download Kotlin today. As mentioned earlier, it is under development. Currently, we have reached the M1 milestone with most important features implemented. While they still require more testing and stabilizing, the compiler and the IDE are good enough for those who want to try the language. The short list of currently available features includes:

  • Mixed Java/Kotlin projects
  • JavaScript back-end
  • Higher-order functions
  • Generics
  • Extension functions
  • Nullable types
  • Basic Ant and Maven support

You can check out the full list in the discussion of Kotlin milestones. The upcoming M2 is planned for sometime before mid-year. The most requested features include:

  • More Nullable/NotNull annotations for core Java libraries
  • KotlinDoc (analogous to JavaDoc, but Markdown-based)
  • More refactorings in the IDE
  • Better support for Maven and Ivy

We plan to start an Eclipse plugin soon, too.

Open Source and "Kontributors"

While there's a whole team working on Kotlin here at JetBrains, the project is open source, and we encourage you to become a "Kontributor," that is a Kotlin contributor. In fact, we've had some people from outside of JetBrains join the project even before the official open-source announcement. Now, let's make the Java platform and the whole world a better place, together!

The purpose of this article is to pique your interest and encourage you to try Kotlin. You will find a lot more details about the language on our wiki. I've included a short screencast illustrating the Kotlin support our IntelliJ plugin provides today.


Andrey Breslav is the lead designer for Kotlin at JetBrains. A full video of his original Kotlin presentation from the JVM Language Summit 2011, along with the slide deck are avaible on Dr. Dobb's.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video