Looking at Kotlin - Fun with functions

Published on 2013-5-21

Onto some of the interesting things you can do with functions in Kotlin...

You can have some real fun with functions in Kotlin, let's do a really basic one first:

Extension methods

Extension methods were exciting a few years ago when they came out in C# because they offered the possibility of creating new and innovative APIs. There is little harm in them being in Kotlin then!

An extension method on the StringBuilder class which when invoked adds a new line to the builder:

fun StringBuilder.newLine() {
    this.append("\n")
}

Simple enough.

Function literals

Any sane language has a way of declaring functions as variables. In JavaScript functions are objects just like anything else so this just works, in C# this ability was added as an afterthought so it is a bit ugly and Java well... it's Java.

A function that takes in nothing and returns nothing in Kotlin looks like:

var function : () -> Unit

If we wanted to invoke this, we could do so very easily

function()

Of course, we'd have to define it first and that looks like this

var function : () -> Unit = {
  println("Hello world")
}

We can be implicit if we're combining declaration and definition so..

var function = {
  println("Hello world")
}

I've got very few comments about this, it's pretty simples and self explanatory.

A few examples to help with the next bits

A function take takes in a string and returns nothing

var print : (arg: String) -> Unit = { (arg: String) ->
  println(arg)
}

or implicitly

var print = { (arg: String) ->
  println(arg)
}

A function that takes in two numbers and returns their sum

var sum = { (one: Int, two: Int) -> one + two }

All of this is pretty nice, although it takes quite a bit of practice remembering what the various forms are for declaring these functions, what threw me (if I understand) is the difference between these function literals and declared functions.

With the function literals, it seems as though the returned value is the last statement in the function

var doubleUp = { (val: Int) -> 
  println("do a thing")
  val * 2 
}

where as in the declared functions it looks like this

fun doubleUp(val: Int) : Int {
  println("do a thing")
  return val * 2
}

I'm not sure I understand this, two different ways of defining functions (plus all the various options we have around being implicit/explicit about the various aspects of the function) made it quite confusing to pick this up and I haven't even gone through some of the examples I saw on the Kotlin website.

Passing functions around

Given the following two methods, I want to invoke applyAndPrint with an array and the doubleUp function

fun doubleUp(value: Int) : Int {
  return value * 2
}

fun applyAndPrint(values: Array<Int>, modifier: (value: Int) -> Int) {
  for(i in values)
    println(modifier(i))
}

My first thought would be

applyAndPrint(array(1,3,4,5), doubleUp)

But this doesn't actually work, which I find counter-intuitive - in fact I couldn't find a good way to do this other than

applyAndPrint(array(1,3,4,5), {(x) -> doubleUp(x)})

I'm definitely missing something here but the docs didn't really help me out.

Setting the context of these passed functions

Okay, this is where things get a bit cooler in theory - what if we had some sort of context (a builder) with some state in it and we want to invoke these functions transparently on top of these state.

Understand that? Well done, I don't - but here's an example

class Builder (val multiplier: Int) {

    fun invokeStuff(action: (Builder.() -> Unit)) {
        this.action()
    }

    fun multiply(value: Int) : Int {
        return value * multiplier
    }
}

The important bit here is the way we've declared the type of 'action'

action: (Builder.() -> Unit)

This is a function that returns nothing, takes in nothing but is invoked on an object of type "Builder".

This means when we use this builder like so

var builder = Builder(10)
builder.invokeStuff({
  var result = multiply(1)
  println(result)
})

The context of 'this' has been set to the builder object and we can invoke functions declared within the builder.

Thing is, this context can be played with - consider that we can also create extension methods on types, let's say the Int type.

class Builder (val multiplier: Int) {

    fun invokeStuff(action: (Builder.() -> Unit)) {
        this.action()
    }

    fun multiply(value: Int) : Int {
        return value * multiplier
    }

    fun Int.doStuff(value: Int) : Int {
      return value * multiplier
    }

}

I can use this with

var builder = Builder(10)
builder.invokeStuff({
  var result = 1.doStuff(50)
  println(result)
})

The scoping of extension functions

This is invalid code

fun someFunction() {
    fun String.someOtherFunction() {

    }
}

fun someStuff() {
    "".someOtherFunction()
}

The extension method isn't visible because it was declared inside a different function.

This is the valid version:

fun someFunction() {
    fun String.someOtherFunction() {

    }
    "".someOtherFunction()
}

The same goes for classes too, this is also invalid

class someClass() {
    fun String.someOtherFunction() {

    }
}

fun someStuff() {
    "".someOtherFunction()
}

The scope is shared outwards when we set the context of those function calls, and this was clearly thought about.

Infix calls

And here is another feature, we have infix calls for functions with only one argument so..

var builder = Builder(10)
builder.invokeStuff({
  var result = 1 doStuff 50
  println(result)
})

My feedback

There are so many things we can do with these functions and the way we do this seems to change with context (which is also something we control - haha, see what I did there?)

There is also power locked up in this state of affairs, I just wish it wasn't so confusing.

I don't see why there is a difference between function literals and declared functions (and if there isn't, I don't know why my examples work or why I'm so confused about it).

Functions are functions are functions, or at least they should be. I should be able to just do

var doSomething = fun(x: Int) : Int {

}

and

map(doSomething)

Being able to pass in/set the context of calls is quite interesting for a specific use case (builders), and I kinda like it - although it feels a bit like something we could abuse horribly (and in fact, in the workshop we did - this is valid Kotlin code)

json {
    "name" .. "Rob Ashton"
    "age" .. 5
    "address" .. {
        "number" .. 444
        "line one" .. "never you mind"
    }
    "family".array(
        {
            "name" .. "Gareth Ashton"
            "relation" .. "brother"
        },
        {
            "name" .. "Suzanne Ashton"
            "relation" .. "sister"
        })
}

What I don't know is all the angles that are trying to be covered, so it's hard to tell why we've ended up in a situation where there is so much to learn about functions.

If you need a page on "this ambiguiguity", then probably you've gone too far.

Like the other language features so far, it feels a bit like there were some cool features thought of, and then things got a little bit of hand as the edge cases were discovered.

I'd just make an effort to prune it back and make things a bit more cohesive. I'd definitely take a look at context and try to avoid the need for pages explaining how to manage it.

2020 © Rob Ashton. ALL Rights Reserved.