First-class and higher-order functions

The most foundational concept of functional programming is first-class functions. A programming language with support for first-class functions will treat functions as any other type; such languages will allow you to use functions as variables, parameters, returns, generalization types, and so on. Speaking of parameters and returns, a function that uses or returns other functions is a higher-order function.

Kotlin has support for both concepts.

Let's try a simple function (in Kotlin's documentation this kind of function is named lambda):

val capitalize = { str: String -> str.capitalize() }

fun main(args: Array<String>) {
println(capitalize("hello world!"))
}

The capitalize lambda function is of type (String) -> String; in other words, capitalize will take String and return another String—in this case, a capitalized String.

As a lambda function, capitalize can be executed using parentheses with parameters (or no parameters at all, depending on the situation).

But what does the (String) -> String type mean?

(String) -> String is a shortcut (some could call it syntactic sugar) for Function1<String, String>Function1<P1, R> is an interface defined in the Kotlin standard library. Function1<P1, R> has a single method, invoke(P1): R, that is marked as an operator (we'll cover operators later).

Kotlin's compiler can translate the shortcut syntax into a fully fledged function object at compile time (indeed, the compiler will apply many more optimizations) as follows:

val capitalize = { str: String -> str.capitalize() }

It is equivalent to the following code:

val capitalize = object : Function1<String, String> {
override fun invoke(p1: String): String {
return p1.capitalize()
}
}

As you can see, the capitalize value's body is located inside the invoke method.

In Kotlin, lambda functions can be used as parameters in other functions as well.

Let's take a look at the following example:

fun transform(str:String, fn: (String) -> String): String {
return fn(str)
}

The transform(String, (String) -> String) function takes one String and applies a lambda function to it.

For all intents and purposes, we can generalize transform:

fun <T> transform(t: T, fn: (T) -> T): T {
return fn(t)
}

Using transform is very simple. Take a look at the following code snippet:

fun main(args: Array<String>) {
println(transform("kotlin", capitalize))
}

We can pass capitalize as a parameter directly, great stuff.

There are more ways to call the transform function. Let's try some more:  

fun reverse(str: String): String {
return str.reversed()
}

fun main(args: Array<String>) {
println(transform("kotlin", ::reverse))
}

reverse is a function; we can pass a reference to it using a double colon (::) as follows:

object MyUtils {
fun doNothing(str: String): String {
return str
}
}

fun main(args: Array<String>) {
println(transform("kotlin", MyUtils::doNothing))
}

doNothing is an object method, and in this case, we use :: after the MyUtils object name:

class Transformer {
fun upperCased(str: String): String {
return str.toUpperCase()
}

companion object {
fun lowerCased(str: String): String {
return str.toLowerCase()
}
}
}

fun main(args: Array<String>) {
val transformer = Transformer()

println(transform("kotlin", transformer::upperCased))

println(transform("kotlin", Transformer.Companion::lowerCased))
}

We can also pass references to instances or companion object methods. But probably the most common case is to pass a lambda directly:

fun main(args: Array<String>) {
println(transform("kotlin", { str -> str.substring(0..1) }))
}

There is a shorter version of this using the it implicit parameter as follows:

fun main(args: Array<String>) {
println(transform("kotlin", { it.substring(0..1) }))
}

it is an implicit parameter (you don't declare it explicitly) that can be used in lambdas with just one parameter.

Although it is tempting to use it for all cases, once you start using it with successive or nested lambdas, they can be difficult to read. Use it sparingly and when it is clear which type it is (no pun intended).

If a function receives a lambda as the last parameter, the lambda can be passed outside the parentheses:

fun main(args: Array<String>) {
println(transform("kotlin") { str -> str.substring(0..1) })
}

This feature opens up the possibility of creating Domain Specific Language (DSL) with Kotlin.

Do you know about the unless flow control statement from Ruby? unless is a control statement that executes a block of code if a condition is false; it's kind of a negated if condition but without an else clause.

Let's create a version for Kotlin by executing the following code snippet:

fun unless(condition: Boolean, block: () -> Unit){
if (!condition) block()
}

fun main(args: Array<String>) {
val securityCheck = false // some interesting code here

unless(securityCheck) {
println("You can't access this website")
}
}

unless receives a condition as a Boolean and blocks to execute as a lambda () -> Unit (no parameters and no return). When unless is executed, it looks exactly like any other Kotlin's control flow structure. 

Now, type alias can be mixed with functions and used to replace simple interfaces. Let's take the following example, our Machine<T> interface from Chapter 1, Kotlin – Data Types, Objects, and Classes:

interface Machine<T> {
fun process(product: T)
}

fun <T> useMachine(t: T, machine: Machine<T>) {
machine.process(t)
}

class PrintMachine<T> : Machine<T> {
override fun process(t: T) {
println(t)
}
}

fun main(args: Array<String>) {
useMachine(5, PrintMachine())

useMachine(5, object : Machine<Int> {
override fun process(t: Int) {
println(t)
}
})
}

It can be replaced with a type alias and used with all the function's syntactical features:

typealias Machine<T> = (T) -> Unit

fun <T> useMachine(t: T, machine: Machine<T>) {
machine(t)
}

class PrintMachine<T>: Machine<T> {
override fun invoke(p1: T) {
println(p1)
}
}

fun main(args: Array<String>) {
useMachine(5, PrintMachine())

useMachine(5, ::println)

useMachine(5) { i ->
println(i)
}
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.173.199