This material was written by Aasmund Eldhuset; it is owned by Khan Academy and is licensed for use under CC BY-NC-SA 3.0 US. Please note that this is not a part of Khan Academy's official product offering.
Like in Python, functions in Kotlin are first-class values - they can be assigned to variables and passed around as parameters. The type a function is a function type, which is indicated with a parenthesized parameter type list and an arrow to the return type. Consider this function:
fun safeDivide(numerator: Int, denominator: Int) = if (denominator == 0) 0.0 else numerator.toDouble() / denominator
It takes two Int
parameters and returns a Double
, so its type is (Int, Int) -> Double
. We can reference the function itself by prefixing its name with ::
, and we can assign it to a variable (whose type would normally be inferred, but we show the type signature for demonstration):
When you have a variable or parameter of function type (sometimes called a function reference), you can call it as if it were an ordinary function, and that will cause the referenced function to be called:
It is possible for a class to implement a function type as if it were an interface. It must then supply an operator function called invoke
with the given signature, and instances of that class may then be assigned to a variable of that function type:
class Divider : (Int, Int) -> Double { override fun invoke(numerator: Int, denominator: Int): Double = ... }
Like in Python, you can write lambda expressions: unnamed function declarations with a very compact syntax, which evaluate to callable function objects. In Kotlin, lambdas can contain multiple statements, which make them useful for more complex tasks than the single-expression lambdas of Python. The last statement must be an expression, whose result will become the return value of the lambda (unless Unit
is the return type of the variable/parameter that the lambda expression is assigned to, in which case the lambda has no return value). A lambda expression is enclosed in curly braces, and begins by listing its parameter names and possibly their types (unless the types can be inferred from context):
val safeDivide = { numerator: Int, denominator: Int -> if (denominator == 0) 0.0 else numerator.toDouble() / denominator }
The type of safeDivide
is (Int, Int) -> Double
. Note that unlike function type declarations, the parameter list of a lambda expression must not be enclosed in parentheses.
Note that the other uses of curly braces in Kotlin, such as in function and class definitions and after if
/else
/for
/while
statements, are not lambda expressions (so it is not the case that if
is a function that conditionally executes a lambda function).
The return type of a lambda expression is inferred from the type of the last expression inside it (or from the function type of the variable/parameter that the lambda expression is assigned to). If a lambda expression is passed as a function parameter (which is the ordinary use) or assigned to a variable with a declared type, Kotlin can infer the parameter types too, and you only need to specify their names:
val safeDivide: (Int, Int) -> Double = { numerator, denominator -> if (denominator == 0) 0.0 else numerator.toDouble() / denominator }
Or:
fun callAndPrint(function: (Int, Int) -> Double) { println(function(2, 0)) } callAndPrint({ numerator, denominator -> if (denominator == 0) 0.0 else numerator.toDouble() / denominator })
A parameterless lambda does not need the arrow. A one-parameter lambda can choose to omit the parameter name and the arrow, in which case the parameter will be called it
:
If the type of the last parameter to a function is a function type and you want to supply a lambda expression, you can place the lambda expression outside of the parameter parentheses. If the lambda expression is the only parameter, you can omit the parentheses entirely. This is very useful for constructing DSLs.
If you want to be more explicit about the fact that you're creating a function, you can make an anonymous function, which is still an expression rather than a declaration:
Or:
Lambda expressions and anonymous functions are collectively called function literals.
Kotlin can get quite close to the compactness of Python's list
/dict
/set
comprehensions. Assuming that people
is a collection of Person
objects with a name
property:
corresponds to
short_greetings = [ f"Hello, {p.name}" # In Python 2, this would be: "Hello, %s!" % p.name for p in people if len(p.name) < 10 ]
In some ways, this is easier to read because the operations are specified in the order they are applied to the values. The result will be an immutable List<T>
, where T
is whichever type is produced by the transformations you use (in this case, String
). If you need a mutable list, call toMutableList()
at the end. If you want a set, call toSet()
or toMutableSet()
at the end. If you want to transform a collection into a map, call associateBy()
, which takes two lambdas that specify how to extract the key and the value from each element: people.associateBy({it.ssn}, {it.name})
(you can omit the second lambda if you want the entire element to be the value, and you can call toMutableMap()
at the end if you want the result to be mutable).
These transformations can also be applied to Sequence<T>
, which is similar to Python's generators and allows for lazy evaluation. If you have a huge list and you want to process it lazily, you can call asSequence()
on it.
There's a vast collection of functional programming-style operations available in the kotlin.collections
package.
The signature of a member function or an extension function begins with a receiver: the type upon which the function can be invoked. For example, the signature of toString()
is Any.() -> String
- it can be called on any non-null object (the receiver), it takes no parameters, and it returns a String
. It is possible to write a lambda function with such a signature - this is called a function literal with receiver, and is extremely useful for building DSLs.
A function literal with receiver is perhaps easiest to think of as an extension function in the form of a lambda expression. The declaration looks like an ordinary lambda expression; what makes it take a receiver is the context - it must be passed to a function that takes a function with receiver as a parameter, or assigned to a variable/property whose type is a function type with receiver. The only way to use a function with receiver is to invoke it on an instance of the receiver class, as if it were a member function or extension function. For example:
class Car(val horsepowers: Int) val boast: Car.() -> String = { "I'm a car with $horsepowers HP!"} val car = Car(120) println(car.boast())
Inside a lambda expression with receiver, you can use this
to refer to the receiver object (in this case, car
). As usual, you can omit this
if there are no naming conflicts, which is why we can simply say $horsepowers
instead of ${this.horsepowers}
. So beware that in Kotlin, this
can have different meanings depending on the context: if used inside (possibly nested) lambda expressions with receivers, it refers to the receiver object of the innermost enclosing lambda expression with receiver. If you need to "break out" of the function literal and get the "original" this
(the instance the member function you're inside is executing on), mention the containing class name after this@
- so if you're inside a function literal with receiver inside a member function of Car, use this@Car
.
As with other function literals, if the function takes one parameter (other than the receiver object that it is invoked on), the single parameter is implicitly called it
, unless you declare another name. If it takes more than one parameter, you must declare their names.
Here's a small DSL example for constructing tree structures:
class TreeNode(val name: String) { val children = mutableListOf<TreeNode>() fun node(name: String, initialize: (TreeNode.() -> Unit)? = null) { val child = TreeNode(name) children.add(child) if (initialize != null) { child.initialize() } } } fun tree(name: String, initialize: (TreeNode.() -> Unit)? = null): TreeNode { val root = TreeNode(name) if (initialize != null) { root.initialize() } return root } val t = tree("root") { node("math") { node("algebra") node("trigonometry") } node("science") { node("physics") } }
The block after tree("root")
is the first function literal with receiver, which will be passed to tree()
as the initialize
parameter. According to the parameter list of tree()
, the receiver is of type TreeNode
, and therefore, tree()
can call initialize()
on root
. root
then becomes this
inside the scope of that lambda expression, so when we call node("math")
, it implicitly says this.node("math")
, where this
refers to the same TreeNode
as root
. The next block is passed to TreeNode.node()
, and is invoked on the first child of the root
node, namely math
, and inside it, this
will refer to math
.
If we had wanted to express the same thing in Python, it would have looked like this, and we would be hamstrung by the fact that lambda functions can only contain one expression, so we need explicit function definitions for everything but the oneliners:
class TreeNode: def __init__(self, name): self.name = name self.children = [] def node(self, name, initialize=None): child = TreeNode(name) self.children.append(child) if initialize: initialize(child) def tree(name, initialize=None): root = TreeNode(name) if initialize: initialize(root) return root def init_root(root): root.node("math", init_math) root.node("science", lambda science: science.node("physics")) def init_math(math): math.node("algebra") math.node("trigonometry") t = tree("root", init_root)
The official docs also have a very cool example with a DSL for constructing HTML documents.
There's a little bit of runtime overhead associated with lambda functions: they are really objects, so they must be instantiated, and (like other functions) calling them takes a little bit of time too. If we use the inline
keyword on a function, we tell the compiler to inline both the function and its lambda parameters (if any) - that is, the compiler will copy the code of the function (and its lambda parameters) into every callsite, thus eliminating the overhead of both the lambda instantiation and the calling of the function and the lambdas. This will happen unconditionally, unlike in C and C++, where inline
is more of a hint to the compiler. This will cause the size of the compiled code to grow, but it may be worth it for certain small but frequently-called functions.
inline fun time(action: () -> Unit): Long { val start = Instant.now().toEpochMilli() action() return Instant.now().toEpochMilli() - start }
Now, if you do:
The compiler will generate something like this (except that start
won't collide with any other identifiers with the same name):
val start = Instant.now().toEpochMilli() println("Lots of code") val t = Instant.now().toEpochMilli() - start println(t)
In an inline function definition, you can use noinline
in front of any function-typed parameter to prevent the lambda that will be passed to it from also being inlined.
run()
, let()
, and with()
?.
is nice if you want to call a function on something that might be null. But what if you want to call a function that takes a non-null parameter, but the value you want to pass for that parameter might be null? Try run()
, which is an extension function on Any?
that takes a lambda with receiver as a parameter and invokes it on the value that it's called on, and use ?.
to call run()
only if the object is non-null:
If maybeNull
is null, the function won't be called, and result
will be null; otherwise, it will be the return value of functionThatCanNotHandleNull(this)
, where this
refers to maybeNull
. You can chain run()
calls with ?.
- each one will be called on the previous result if it's not null:
The first this
refers to maybeNull
, the second one refers to the result of firstFunction()
, and result
will be the result of secondFunction()
(or null if maybeNull
or any of the intermediate results were null).
A syntactic variation of run()
is let()
, which takes an ordinary function type instead of a function type with receiver, so the expression that might be null will be referred to as it
instead of this
.
Both run()
and let()
are also useful if you've got an expression that you need to use multiple times, but you don't care to come up with a variable name for it and make a null check:
Yet another version is with()
, which you can also use to avoid coming up with a variable name for an expression, but only if you know that its result will be non-null:
In the last line, there's an implicit this.
in front of both memberFunction()
and memberProperty
(if these exist on the type of someExpression
). The return value is that of the last expression.
apply()
and also()
If you don't care about the return value from the function, but you want to make one or more calls involving something that might be null and then keep on using that value, try apply()
, which returns the value it's called on. This is particularly useful if you want to work with many members of the object in question:
maybeNull?.apply { firstFunction(this) secondFunction(this) memberPropertyA = memberPropertyB + memberFunctionA() }?.memberFunctionB()
Inside the apply
block, this
refers to maybeNull
. There's an implicit this
in front of memberPropertyA
, memberPropertyB
, and memberFunctionA
(unless these don't exist on maybeNull
, in which case they'll be looked for in the containing scopes). Afterwards, memberFunctionB()
is also invoked on maybeNull
.
If you find the this
syntax to be confusing, you can use also
instead, which takes ordinary lambdas:
maybeNull?.also { firstFunction(it) secondFunction(it) it.memberPropertyA = it.memberPropertyB + it.memberFunctionA() }?.memberFunctionB()
takeIf()
and takeUnless()
If you want to use a value only if it satisfies a certain condition, try takeIf()
, which returns the value it's called on if it satisfies the given predicate, and null otherwise. There's also takeUnless()
, which inverts the logic. You can follow this with a ?.
to perform an operation on the value only if it satisfies the predicate. Below, we compute the square of some expression, but only if the expression value is at least 42:
← Previous: Null safety | Next: Packages and imports → |
© 2010–2019 JetBrains s.r.o.
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/tutorials/kotlin-for-py/functional-programming.html