Introduction.
Previous pages have introduced the concept of lambdas and closures, and explained how the concept of the closure relates to methods, static and class methods in both python and kotlin. This page builds on the concept of lambdas and closures, and provides more details on the workings in kotlin, and how this differs from other languages. This page looks at not just the python equivalent, but also differences from groovy, as a common use case right now is converting lambdas from groovy lambdas to kotlin, for gradle scripts.
- Closures and Lambdas
- Anonymous Functions? Or simply, Literal Code
- First Class Functions?
- When to use Lambdas vs Named Functions
- Functions as data 1: Passing Functions as Parameters
- callbacks
- Lambda Parameters and types: The power of kotlin.
- Functions as data 2: Returning a function
- Functions as data 3: storing functions in variables
- Converting Groovy Lambdas
- Conclusion
Closures and Lambdas
As already covered, closures are units of code that can access all of attributes from surrounding scope, even when executed outside that scope. Most units of code only execute within their surrounding scope. For code to execute within another scope, the unit of code must be passed to another scope and then called from that other scope. Code which can be called from another scope is known as a function, and lambdas are just a special type of function, and lambdas are also called an anonymous functions, however the label ‘literal code’ is also very descriptive.
Anonymous Functions? Or Simply, Literal Code
Consider the number of days of the week. Code that manipulates number of days can divide by 7 to calculate number of weeks. However, this code is discouraged, and it is recommended to define a constant DAYS_IN_WEEK, as having the name is better than the anonymous literal. So why have anonymous literal code in place of a name for the code, as a function definition would provide? The answer is that while there are cases like the DAYS_IN_WEEK where the literal value has no meaning, and constants that are reused many times, there is still a place for literal values, and the same logic shows a place for literal code. Consider the code to add one in python: value += 1
. Should we replace the literal with a constant? Or a test to see if someone entered “The quick brown fox jumps over the lazy dog”. In both the += 1 and “the quick” no constant name would be more descriptive than the literal. In fact, the reverse happens, and it is needed to check the constant definition to be sure what is happening, when that is no needed with the literal. The same can happen with code, and particularly with very small blocks of code, in that the code can be just as clear as the name, and can have having to look elsewhere for a definition of the function.
First Class Functions.
As stated before, closures are about code that can passed from scope to scope just as can be done with data. The code to be passed can be in the form of either regular functions or lambdas (literal/anonymous functions). The ability of functions to be passed from scope to scope just as with data, is referred to as functions first class functions and means that functions can be returned as a result, passed as a parameter and stored in a variable.
Lambdas for passing code as a parameter, and an introduction to lambdas are discussed in the previous page Lambdas: What do you mean I will use them?
Also see ‘what are closures‘ for an example of a python function that returns a function as a result. That example is a simple closure. That page is more focused on how class methods in both python and kotlin, act as closures in providing access to the scope of the class at all times (although is slightly different ways).
When to use Lambdas vs Named Functions Kotlin?
Basically, anytime Kotlin code is being treated as data (stored in a variable, passed as a parameter or returned as result) use a lambda, in place of directly using a named function. This means if you have a named function, simply enclose the call to that function in braces to make a lambda.
Consider the example from the into to lambdas page, which sorts a list of names by the last name of each person. Now imagine that we already have a function “lastWord” to extract the last name of people in the list. Consider the code directly using the “lastWord” function compared to using a lambda. Which do you find clearer?
// original code println(lst.sortedBy{ it.split(" ".last()) //now our function that we will assume exists already fun lastWord(words:String) = words.split(" ").last() // now using the named function form println(lst.sortedBy(::lastWord)) //now the preferred wrapping the function as a lambda form println(lst.sortedBy{lastWord(it)})
You could complain the lambda form is two characters longer, but now consider it is clearer to read that sortedBy takes a lambda parameter, the form is more consistent with the more common case of where there is no function already available. The benefits grow as the parameter mapping gets more complex, and the IDE support is far better.
Also consider that the code with the ‘lastWord’ function (whether in named function or lambda form) is overall longer than the original form, and actually harder to read. The lesson here, is that very small code is easier to read in place in a lambda, and having a function becomes appropriate the greater the amount of code in the function. A function which as this one, simply does two other functions doesn’t help readability, while the name of a 20 line function may convey what it does without the overhead of reading that more complex function.
The syntax of kotlin makes the ‘always in a lambda’ approach the simplest, far less prone to error, and best supported by with code completion by IDEs. The special syntax for accepting lambdas as parameters outside the braces, is all linked to the ‘pass code within lambdas” is idiomatic kotlin.
A very short lambda is highly readable, and more readable and explanatory than a function name for code that is self explanatory.
A long complex lambda will fail that high readability test. The solution is: move complex code within the lambda to well named functions, so the lambda itself is reduced to being short, clear, and readable.
Functions as data 1: Passing Functions as Parameters
Callbacks
What is a callback? In some ways the example above using ‘sortedBy’ uses a callback. Sorted by has a parameter (named ‘selector’) which is the code to extract the value to be sorted. The sortedBy function makes a ‘call back’ to that code provided as a selector with each string in the list to get the value from the string for sorting (in the example, to get the last name). The syntax is the same as a callback, but something people thing more of “call me back later”. On the phone you may ask someone to call you back when they get your message. Callbacks with programming are more “run this code when something happens”.
For example, you call a function to draw a text box on the screen. With a console program, the program would wait for the input, but with a GUI program, the program draws the textbox and returns to the calling code. An example of a callback is a set of instructions to be run in future when text is entered into the textbox.
textBox("Label for Box", width= 10, callBack= {saveText(it)})
With code, a ‘callback’ is instructions you supply to a call a function, where something resulting from that function be left running. A textbox on the screen has running code ready to accept input even after the call to draw the textbox has finished. The textbox will callback the ‘saveText()’ funtion with the text that was entered. This callback may happen some time minutes after the program called the draw ‘textBox’ function, and that drawtext box function returned immediately, but then ‘called back’ to the drawtext when some one typed text into the textbox.
The same “use a lamba” in place of “::functionName” applies for readable code as “when to use lambdas vs function names. Also using lambdas allows reusing the same function. Code converted to Kotlin from languages without lambdas will tend to have a purpose built function taking exactly the parameters of the callback, even if the function makes no use of some of the parameters. Lambdas provide a far more readable solution. Create a function taking only the parameters required, and then enclose the call to that function in a lambda. Very often as code grows, the same function can be reused in several lambdas by supplying custom parameters in each lambda. For example there could be buttons on a GUI for each numeral. A button for ‘1’ with a “::pressOne” callback, another for ‘2’ with a “::pressTwo callback etc. The pressOne and pressTwo function may need to accept parameters such as “GuiContext” or something, even if they do not need these values. Without the right parameters, the functions are not permitted. But a lambda will always work. Kotlin makes it easy to have a {press(1)} lambda and a {press(2)} lamba, making use of the same “press” function for each numeral on the graphic keypad, and the ‘press’ function needs no dummy paramerters.
Lambda Parameters and types: The Power of Kotlin.
Using callbacks just involves providing functions as a parameter, but you both sides of the picture of functions as a parameter become clear when declaring, and then using, functions which take functions as a parameter.
There are two simple rules for declaration of parameters within the lambda, plus a third rule about what you don’t declare. The simple syntax hides the fact that, as a statically typed language, types must be specified. Kotlin can always infer parameter and return types from the receiver of the function, so there is not even syntax to declare types within a lambda. This makes lambdas simpler than even with a dynamically typed language such as python or groovy that cannot rely on inferring types. The power of kotlin is that the benefit of having types comes while having simpler code and is implicit.
The rules:
- If there is only one parameter, no parameter definition is required and the parameter will be named ‘it’.
- Parameters inside the lambda are declared by a list of names (simple replacing ‘it’ if there is only one parameter) followed by the
->
characters. - There is no need to declare types in the lambda, as lambda
Consider this simple which defines a function which accepts three parameters, two Int parameters, and a third first class function parameter.
There is then an example of calling this function, providing Ints 1 and 2 and a lambda which returns the sum of the two integers.
fun calc(a: Int, b: Int, code: (Int,Int)-> Int): Int{ return code(a,b) } calc( 1, 2 ){ num1, num2 -> num1 + num2 }
Supplying the lambda to call to ‘calc’ is deceptively simple. Just give a name for the parameters to the lambda, then add the code. The result of the last statement will be the return value, and often there is only one statement anyway.
Ok, so what about those static types?
This can seem even simpler than a groovy lambda, where the syntax is similar but the parameters can optionally specify type. How in a statically typed language where types are mandatory, is there no syntax for declaring parameter or return types? The answer is that types are declared for the receiver of the lambda, not the lambda so they are already declared.
The definition of fun ‘calc’ includes the third parameter to be a function. A function parameter has the types of the parameters, or empty () brackets if there are no, parameters, then the ‘->’ and the return type. If the return value is not used, the return type should be ‘Unit’ and a return type must always be declared.
In this example, ‘code’ is declared to be a function that accepts two Int (integer) parameters, and returns an Int result. The compiler knows that the lambda is being supplied to ‘calc’ so, therefore, has two parameters, both of type Int, and returns an Int. The lambda does not declare any types because the types have been predetermined. So every lambda has the types set by the need the lambda is fulfilling, while the names, which are used within the lambda, are specified in the code of the lambda itself. Different lambda blocks supplied to ‘calc’ could use different parameter names, but cannot use different parameter types, or return a different type.
Functions a data part 2: Returning a function.
Here is a very simple function which returns a function. The function returned takes two Int(egers), adds them, and returns the result.
fun giveMeAnAdder():(Int,Int)->Int{ return {a,b-> a+b} }
As you can see, returning a function follows the same patterns and syntax rules as those for passing functions as parameters.
Functions as data 3: storing functions in variables
When first considering storing functions in variables, it can appear that using lambdas will be a challenge.
Lambdas as function parameters is the most common case, and the types for the parameter and return are already specified by the function receiving the lambda (receiving the lambda meaning can also be stated as ‘accepting the lambda as parameter’).
Storing the lambda in as data is storing the lambda for future use, and it is that future use that will specify the types, so it can appear the IDE assistance and even compiler checking that the types will be correct must be impossible, since the use is in the future.
How can we define the parameter and return types for a lambda, if those types are declared in the context(in this case, a function) definition, and lambdas we are writing are not yet linked to a context (or function)?
The answer is that vals and vars can also be a context and define those types. A change in thinking is still needs because those types are automatically available when simply typing a lambda which will be passed as a parameter, but for the var or val declaration, it is necessary to think ahead about the types required by a function that may later receive the stored lambda as a parameter. The easy way is to look at the parameter type for the lambda in a function declaration that may later use the lambda data, and copy that lambda type declaration as the type for the var or val.
Examples
val adder: (Int,Int)->Int = {a,b -> a+b} val lambdaWithNoParameter = { 4 } // the return type is inferred val lambdaWithOneParameterAndUsingItForName: (Int) -> Int = { it+1 }
the type for the var or val provides the context. While the return type can be inferred in most cases, since the type of parameters cannot be inferred, the var or val will normally need an explicit type.
As a strongly typed language, all types (parameters and return type) are set from the outset, before the lambda code is saved. The advantage is IDE support for getting the lambda correct.
The main reason to store a lambda is to use it several times. If almost the same lambda, but not exactly the same, is needed , then stored lambda could be called from with another lambda at the point of usage that deals with the differences in usage.
The examples above are val, and as such it would seem little different from a fun declaration in practice, since these also define a block of code. A var could better justify using an alternate syntax, since then program logic could result in different possible code being stored in the variable, which does not happen with a fun.
Converting Groovy
Groovy is in many ways half way between kotlin and python, so the comparison can be interesting. Further, the move from groovy to kotlin for gradle files creates a need to do conversions.
Consider this code found in a code snippet of a gradle file for building kotlin-js files
</pre> // code from https://github.com/bascan/kotlin-js-example/blob/master/build.gradle def jsFileFilter = { fileTreeElement -> def path = fileTreeElement.path path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) } configurations.compile.each { File file -> copy { includeEmptyDirs = false from zipTree(file.absolutePath) into "${project.buildDir}/classes/main/lib" include(jsFileFilter) } } configurations.testCompile.each { File file -> copy { includeEmptyDirs = false from zipTree(file.absolutePath) into "${project.buildDir}/classes/test/lib" include(jsFileFilter) } } <pre>
and now the kotlin
</pre> // Augmented from https://github.com/bascan/kotlin-js-example/blob/master/build.gradle val jsFileFilter: (FileTreeElement)-> Boolean = { fileTreeElement -> val path = fileTreeElement.path path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) } configurations.compile.forEach { file -> copy { includeEmptyDirs = false from(zipTree(file.absolutePath)) into("${project.buildDir}/classes/main/lib") include(jsFileFilter) } } configurations.testCompile.each { file -> copy { includeEmptyDirs = false from(zipTree(file.absolutePath) into("${project.buildDir}/classes/test/lib") include(jsFileFilter) } } <pre>
The original code uses double quotes, so that saves one step. The normal next step is to add any implied brackets or = by looking for consecutive identifiers. This reveals ‘from’ ‘into’ and ‘include’ which all require brackets.
Now the conversion of the stored lambda. Groovy makes it clear how many arguments there are, but what type? The variable name is good clue, and the last statement is clearly a Boolean so we have the return type. All done 🙂
Conclusion
First class functions provide code injection, which is an extremely powerful tool. Lambdas are literals for code, and even simple function calls are best wrapped in a lambda, as unlike other literals, the explanatory name (the name of the function) is still completely clear, the lambda just makes it clearly that it is a function and what is happening with parameters.