Functional programming

What is functional programming? Here’s what Wikipedia says:

Functional programming, or functional programming, is a programming paradigm that treats computer operations as functional operations and avoids the use of program states and mutable objects. Among them, lambda calculus is the most important foundation of the language. Furthermore, a function of the lambda calculus can accept functions as inputs (arguments) and outputs (outgoing values). Compared with instruction programming, functional programming emphasizes the results of program execution rather than the process of execution. It advocates gradual calculation of results by using several simple execution units, and deduces complex operations layer by layer rather than designing a complex execution process.

After understanding the concept of this trend, it will be good to have an impression. The following sentence is important:

In functional programming, a function is a first-class object, meaning that a function can be modified or assigned to a variable either as an argument to other functions (input values) or as a return from a function (input values).

Isn’t that the definition of higher-order functions? Yes, however, functional programming is a programming paradigm, and higher-order functions are the basic techniques of functional programming, in other words: functional programming includes but is not limited to higher-order functions as a technique, as well as closures, Currization, partial functions, and so on.

closure

Closures are functions that can read variables inside other functions. For languages that support functional programming, it is possible to define A function B inside A function A, and function B can access the members of function A. Usually function A returns function B, and closures extend the life of function A (members in function A are not recycled).

The relationship between two functions in a closure is similar to the relationship between an inner class and an outer class, but the closure is lighter.

We can use closures to implement Fibonacci sequences. The key point of Fibonacci sequences is to cache previous results for subsequent computations. Closures are appropriate:

fun fib(a): () - >Long {
    var prev = 0L
    var current = 1L
    return fun(a): Long {
        val result = current
        current += prev
        prev = result
        return result
    }
}
fun main(a) {
    val fib = fib() // fib is an anonymous function in fib()println(fib())/ / 1
    println(fib()) / / 1
    println(fib()) / / 2
    println(fib()) / / 3
    println(fib()) / / 5
    println(fib()) / / 8
}
Copy the code

As you can see, each time the fib anonymous function is called, the result is different, indicating that the closure does allow the inner function to hold the members of the outer function and extends the life of the variables in the scope of the outer function.

Currie,

What is Corrification? Here’s a Wikipedia definition of Currification:

In computer science, Currying, also translated as carryization or callization, is a technique of converting a function that takes multiple arguments into a function that takes a single argument (the first argument of the original function), and returning a new function that takes the remaining arguments and returns a result.

Suppose we have a summation function add, which takes multiple arguments and satisfies the corrification of the function:

fun add(num1: Int, num2: Int): Int {
    return num1 + num2
}
fun main(a) {
    val result = add(1.2)
    println(result) / / 3
}
Copy the code

How do we get to the cremation of add? According to the Cremation definition, we need to make two changes to the function add:

  • Parameter list: Converts receiving multiple parameters to receiving a single parameter.
  • Return value: Returns the function that receives the remaining arguments and results.
fun add(num1: Int): (Int) - >Int {
    return fun(num2: Int): Int {
        return num1 + num2
    }
}
fun main(a) {
    val result = add(1) (2)
    println(result) / / 3
}
Copy the code

You can see that the form of the call to add has changed, from add(1, 2) to Add (1)(2), and that’s currization. In addition, since Kotlin supports intelligent type derivation, the Corrification function add can also be simplified:

fun add(num1: Int) = fun(num2: Int) = num1 + num2
Copy the code

This change is also amazing, the simplified Currization function add is a surprise. Similarly, if the number of arguments is 3,4,.. N. For Kotlin, currizing them is a matter of shuttles.

Partial function

Partial functions are Partial functions that fix some of the parameters of a function (that is, set the default values) and return a new function that is easier to call.

Note: Partial functions are similar to function default arguments, but there are differences. The default parameter is based on the original function to specify the default value of some parameters, when the original function is called, while the partial function is usually by some means generated by a new function, the subsequent call of the new function.

For example, there is a function buildString that builds different encoding strings:

fun buildString(byteArray: ByteArray, charsetName: String): String {
    return String(byteArray, charset(charsetName))
}
fun main(a) {
    val result = buildString("lqr".toByteArray(), "UTF-8")
    println(result)
}
Copy the code

Now programming uses UTF-8 as the default encoding, so instead of passing charsetName every time this function is called, you can specify the default encoding for this function.

fun buildString(byteArray: ByteArray, charsetName: String = "UTF-8"): String {
    return String(byteArray, charset(charsetName))
}
fun main(a) {
    val result = buildString("lqr".toByteArray())
    println(result)
}
Copy the code

What difference does it make if we use partial functions? Unfortunately, Kotlin itself does not provide a functools.partial function similar to Python’s functools.partial function, but we can use extension functions to achieve similar partial function generation:

// Extend Function2, partial2 can generate partial functions with default argument 2, and partial1 can generate partial functions with default argument 1
fun <P1, P2, R> Function2<P1, P2, R>.partial2(p2: P2) = fun(p1: P1) = this(p1, p2)
fun <P1, P2, R> Function2<P1, P2, R>.partial1(p1: P1) = fun(p2: P2) = this(p1, p2)

fun main(args: Array<String>) {
    val buildStrPartial2 = ::buildString.partial2("UTF-8")
    val result = buildStrPartial2("lqr".toByteArray())
    println(result)
}
Copy the code

In my opinion, using partial functions in this way in Kotlin at this stage is better than using the default parameters of the function. For partial functions, it is good to know what they are.