[Kotlin Pearls 4] It’s an Object… It’s a Function… It’s an Invokable
Currying the Kotlin Way
This is the third article in a series. About little known features of Kotlin.
You can find the previous post here.
When I started looking at other people’s Kotlin code, I was very surprised to see a class like this:
class ReceiptText(val template: String): (Int) -> String { override fun invoke(amount: Int): String =
template.replace("%", amount.toString())
}
Inheriting a class from a function? I was seriously confused!
I really shouldn’t have. Function Types work just other types in Kotlin; you can use them as parameters, as variables and so on, so why not inheriting from them?
If you inherit from a Function Type the compiler will force you to override the invoke
function with the matching parameter and return types.
What is the invoke
function? It is the function “without a name”, so you will just put the parenthesis after the object name.
In Functional Programming, Function Types are called Arrow Types because they express a morphism from a type to another one. They are part of the set composite types, like the generics.
That is enough theory for the moment, let’s look instead at what we can do with them.
When working with Function Types in Kotlin is also convenient to define a type alias like this:
typealias FIntToString = (Int) -> String
I deliberately avoided to use it in the examples of this post to make them more evident, but I usually define typealias
in my production code.
As an example, let’s say we need to print messages for a receipt email, with reference to the sum received. We also have to leave the text template configurable by users.
We can start writing a function with two parameters:
fun receipt(template: String, amount: Int) =
template.replace("%", amount.toString())
And then using it like this:
val text = receipt(readTemplateFromConf(), amount)
But this approach requires that we have the template from configuration and the amount at the same time.
In many situations, we would like instead to read the configuration once at start-up and then print all the messages when we read the amounts from the donations database.
Using Function Type classes, we can easily separate the reading of text template from the generation of the message with the amount:
val receipt = ReceiptText("Thank you for you donation of $%!")//do something else...val text = receipt(123) // "Thank you for you donation of $123!"
Another solution is a function with the template parameter that returns a lambda that will accept the amount parameter:
fun receiptText(template: String): (Int) -> String = {
amount -> template.replace("%", amount.toString())
}
Comparing these two approaches, I think the first one, based on a class, is easier to read and allows for more complicated set-ups.
The second one is called a Higher Order Function, and it is more concise but a bit harder to read, so I tend to use it only for very simple cases.
Another way to use the special of invoke
function is defining the a new one as operator.
sealed class TemplateString//an object with invoke operator
object ReceiptTextObj : TemplateString() {operator fun invoke(amount: Int): String =
receiptText("My receipt for $%")(amount)operator fun invoke(template: String, amount: Int): String =
receiptText(template)(amount)
}
Both objects and classes can have multiple operator invoke
functions. I found it especially useful when working with sealed classes.
Note that the object ReceiptTextObj
is not inheriting from our type (Int) -> String
, but its invoke
method is of the same type.
It is not the topic of this post, but keep in mind that invoke functions with a block as the only parameter are very useful for when you create a DSL in Kotlin.
Since Function Types works like normal types, can we use them for generic collections? Sure we can!
We can easily verify this by putting our three examples into a list and then call them with different amounts:
val functions = mutableListOf<(Int) -> String>()functions.add(receiptText("TA %"))
functions.add(ReceiptText("Thank you for $%!"))
functions.add(ReceiptTextObj::invoke)val receipts = functions.mapIndexed{i, f -> f(i+100) }
//["TA 100", "Thank you for $101!", "My receipt for $102"]
Depending on the use case a syntax can be better than the others. I am interested to learn how you use them and other possible uses.
More generally speaking, this technique is called Currying, from the mathematician Haskell Curry.
It transforms a function with many parameters in a series of single parameter functions. Each one returning another function with one parameter less.
from this:
(String, Int, Double) -> Personto this:
(String) -> (Int) -> (Double) -> Person
As another example, we can define a builder for a complex object in this way:
data class Person(val name: String, val age: Int, val weight: Double)val personBuilder: (String) -> (Int) -> (Double) -> Person =
{ name ->
{ age ->
{ weight ->
Person(name, age, weight)
}
}
}
And then we can use it in this way:
val frank = personBuilder("Frank")(32)(78.5)
//Person(name=Frank, age=32, weight=78.5)")val names = listOf("Joe", "Mary", "Bob", "Alice")val people: List<Person> = names
.map { personBuilder(it) } //choose the name
.map { it(nextInt(80)) } //a random age
.map { it(nextDouble(100.0)) } //a random weight
//4 random Person instances
Currying is a very important and powerful tool for the functional programmer.
Finally a consideration about how these functions and invokable are translated in byte code. You can easily verify by yourself with the useful IntelliJ function to show bytecode generated by Kotlin source.
Classes with Function Type: INVOKESPECIAL is how calls to non virtual methods are done in the JVM. Here it is the normal object constructor, then the function-like call is a call to the invoke virtual method.
LINENUMBER 12 L0
NEW com/ubertob/invokeOperator/ReceiptText
DUP
LDC "Thank you for you donation of $%!"
INVOKESPECIAL com/ubertob/invokeOperator/ReceiptText.<init> (Ljava/lang/String;)V
ASTORE 1
L1
LINENUMBER 14 L1
ALOAD 1
BIPUSH 123
INVOKEVIRTUAL com/ubertob/invokeOperator/ReceiptText.invoke (I)Ljava/lang/String;
ASTORE 2
Functions returning a lambda: Kotlin functions are static methods on a hidden class created by the file, so to call the function we use an INVOKESTATIC call.
Then the actual lambda is an INVOKEINTERFACE call, since Kotlin lambdas are mapped over single method interfaces (a.k.a SAM) on the JVM, exactly like Java lambdas.
L0
LINENUMBER 25 L0
LDC "Thank you for you donation of $%!"
INVOKESTATIC com/ubertob/invokeOperator/InheritingFromFunctionsKt.receiptText (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
ASTORE 1
L1
LINENUMBER 27 L1
ALOAD 1
BIPUSH 123
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
INVOKEINTERFACE kotlin/jvm/functions/Function1.invoke (Ljava/lang/Object;)Ljava/lang/Object; (itf)
CHECKCAST java/lang/String
ASTORE 2
Objects with invoke operator: Kotlin Objects are final static instances on the JVM and invoke is seen as a normal virtual method.
L0
LINENUMBER 37 L0
GETSTATIC com/ubertob/invokeOperator/ReceiptTextObj.INSTANCE : Lcom/ubertob/invokeOperator/ReceiptTextObj;
BIPUSH 123
INVOKEVIRTUAL com/ubertob/invokeOperator/ReceiptTextObj.invoke (I)Ljava/lang/String;
ASTORE 1
All sources are on GitHub (https://github.com/uberto/kotlin-pearls)
Update (21/4/2019): A friend asked me an example of invoke used with a block for a DSL. Here it isL
object Console {
operator fun invoke (block: (String) -> String): Nothing {
while (true) {
val l = readLine()
if (!l.isNullOrBlank())
println(block(l))
}
}
}
The idea is that a Console
s object runs an infinite loop of reading the standard input and passing the value to the block.
It can be used to parse commands from user and giving back answers, like this:
fun main() {
println ("Write your command!") Console {
val parts = it.split(' ')
when (parts[0]) {
"go" -> "going ${parts[1]}"
"eat" -> "eating ${parts[1]}"
"quit" -> throw InterruptedException("Program has been terminated by user")
else -> "I don't think so..."
}
}}
If you like this post, please applaud it and follow me on Twitter and Medium.