Funtional Programming (FP) is a Programming paradigm, a methodology for how to write programs.

What is functional programming?

1.1. Introduction

Main idea: the calculation process as far as possible into a series of reusable function calls. Main features: Functions are “first-class citizens” (functions have the same status as other data types, can be assigned to other variables, can be function arguments, function return values).

A few common concepts in functional programming:

  • Higher-order Function, Function Currying
  • Functor, Applicative Functor, Monad

Extension: Functional programming first appeared in LISP, and most modern programming languages support functional programming to varying degrees, such as JavaScript, Python, Swift, Kotlin, Scala, etc.

Practice of 1.2.

1.2.1. Traditional writing

Sample code:

var num = 1
func add(_ v1: Int._ v2: Int) -> Int { v1 + v2 }
func sub(_ v1: Int._ v2: Int) -> Int { v1 - v2 }
func multiple(_ v1: Int._ v2: Int) -> Int { v1 * v2 }
func divide(_ v1: Int._ v2: Int) -> Int { v1 / v2 }
func mod(_ v1: Int._ v2: Int) -> Int { v1 % v2 }

// [(num + 3) * 5 - 1] % 10 / 2
print(((num + 3) * 5 - 1) % 10 / 2) // Output: 4
var result = divide(mod(sub(multiple(add(num, 3), 5), 1), 10), 2)
print(result) // Output: 4
Copy the code

As you can see, there is a lot of nesting above, and the entire code is very poorly read and written if you encounter complex scenarios.

1.2.2. Functional writing

If you assign the add function to a variable, and the function always adds a number, it’s very convenient to write functionally.

Sample code;

func add(_ v: Int)- > (Int) - >Int{{$0 + v} }
var addFn = add(3)
print(addFn(5)) // Output: 8
print(addFn(20)) // Output: 23
print(addFn(66)) // Output: 69
Copy the code

Add () returns a function, which can also be called with an argument. The end result is to add the two arguments together. And that way you can reuse the function.

In the same way, other functions are treated functionally:

typealias FnInt = (Int) - >Int
func add(_ v: Int) -> FnInt{{$0 + v} }
func sub(_ v: Int) -> FnInt{{$0 - v} }
func multiple(_ v: Int) -> FnInt{{$0 * v} }
func divide(_ v: Int) -> FnInt{{$0 / v} }
func mod(_ v: Int) -> FnInt{{$0 % v} }

let fn1 = add(3)
let fn2 = multiple(5)
let fn3 = sub(1)
let fn4 = mod(10)
let fn5 = divide(2)

let result = fn5(fn4(fn3(fn2(fn1(num)))))
print(result) // Output: 4
Copy the code

I found that the code still looked bloated using the above method. Here’s how function composition solves this problem.

Second, function synthesis

Function composition, in fact, is to combine multiple functions into one function.

Sample code (function composition) :

func composite(_ f1: @escaping FnInt._ f2: @escaping FnInt) -> FnInt {
    { f2(f1($0))}}let fn = composite(add(3), multiple(5))
Copy the code

So you combine the addition and multiplication functions into one function, and the other functions are the same. If we want the function to be more elegant, we can customize the operator.

Example code (custom operator) :

infix operator >>> : AdditionPrecedence
func >>>(_ f1: @escaping FnInt._ f2: @escaping FnInt) -> FnInt {
    { f2(f1($0))}}let fn = add(3) >>> multiple(5) >>> sub(1) >>> mod(10) >>> divide(2)
let result = fn(num)
print(result) // Output: 4
Copy the code

Fn (num) returns the same type of argument as f2. Fn (num) returns f2.

To ensure reusability of functions, generics are used to express them:

func >>> <A.B.C> (
    _ f1: @escaping (A) - >B._ f2: @escaping (B) - >C)- > (A) - >C {
    { f2(f1($0))}}Copy the code

Higher order functions

A higher-order function is one that satisfies at least one of the following conditions:

  • Take one or more functions as input (for example:Map, Filter, and ReduceEtc.)
  • Return a function

FP is full of higher order functions. Examples include add, sub, multiple, divide, and mod in the above sample code.

4. Corrification

What is a Currying? Transform a function that takes many arguments into a series of functions that take only one argument.

Example code 1 (base code) :

func add(_ v1: Int._ v2: Int) -> Int { v1 + v2 }
add(10.20)
Copy the code

Example code two (Currization) :

func add(_ v: Int)- > (Int) - >Int{{$0 + v} }
add(10) (20)

// The original function takes a Currization of two arguments
func currying<A.B.C> (_ fn: @escaping (A.B) - >C)- > (B) - > (A) - >C {
    {
        b in {
            a in fn(a, b)
        }
    }
}
currying(add)(20) (10)

// Override the ~ operator
prefix func ~ <A.B.C>(_ fn: @escaping (A.B) - >C) - > (B) - > (A) - >C {
    {
        b in {
            a in fn(a, b)
        }
    }
}
let fn = (~add)(3) >>> (~multiple)(5) >>> (~sub)(1) >>> (~mod)(10) >>> (~divide)(2)
print(fn(1)) // Output: 4
Copy the code

The process from example code one to example code two is called Cremation. And when you use the generic Currization function, you only need to call the generic Currization function when you use the original function of the same type.

The map methods of Array and Optional take arguments that are currified.

Example code three (the original function receives a cremation of three arguments) :

func add1(_ v1: Int._ v2: Int._ v3: Int) -> Int { v1 + v2 + v3 }

// add1 corrified
func add2(_ v3: Int)- > (Int) - > (Int) - >Int {
    // v2 == 20
    return { v2 in
        // v1 == 10
        return { v1 in
            return v1 + v2 + v3
        }
    }
}
add2(30) (20) (10)

// The original function takes the currization of three arguments
func currying<A.B.C.D> (_ fn: @escaping (A.B.C) - >D)- > (C) - > (B) - > (A) - >D {
    {
        c in {
            b in {
                a in fn(a, b, c)
            }
        }
    }
}
currying(add2)(30) (20) (10)
Copy the code

Note: The Parameters of the Currization function are generally in reverse order of the parameters of the original function. Adjust the sequence of parameters based on development requirements.

Fifth, functor

Types that support map operations, such as Array and Optional, are called functors.

Array map definition:

public func map<T> (_ transform: (Element) throws -> T) rethrows- > [T]
Copy the code

The map function returns an array. The closure expression returns a value and the array returns an entity that is a generic T. Satisfies the conditions defined by functors.

Optional map definition:

public func map<U> (_ transform: (Wrapped) throws -> U) rethrows -> U?
Copy the code

Wrapped is an entity of optional type, the map function returns an optional type, and the closure expression returns a value and an array contains an entity of generic TYPE U. Satisfies the conditions defined by functors.

For example, if there is an optional type, the internal package is 2. To perform the +3 operation, unpack the number 2 first and put the result back into the box (optional type) after the +3 operation. If the box is empty, no operation is performed. Here’s the legend:

Step 1: Value

Step 2: Operation repackaging

Step 3: Null values are not processed

Example of an array map (iterating over each value and placing the value in a new array) :

5.1. Applicable functors

Any Functor F is an Application Functor if it can support the following operations.

// Pass in any generic parameter A, and finally return the entity of the corresponding type
func pure<A> (_ value: A) -> F<A>
func < * > <A.B>(fn: F<A -> B>, value: F<A- > >)F<B>
Copy the code

Optional can be an applicable functor:

func pure<A> (_ value: A) -> A? { value }
infix operator < * > : AdditionPrecedence
func < * > <A.B>(fn: ((A) - >B)?, value: A?). ->B? {
    guard let f = fn, let v = value else { return nil }
    return f(v)
}

var value: Int? = 10
var fn: ((Int) - >Int)? = { $0 * 2}
print(fn < * > value as Any) // Output: Optional(20)
Copy the code

Array can be an applicable functor:

infix operator < * > : AdditionPrecedence
func pure<A> (_ value: A)- > [A] { [value] }
func < * > <A.B>(fn: [(A) - >B], value: [A]) -> [B] {
    var arr: [B] = []
    if fn.count = = value.count {
        for i in fn.startIndex..<fn.endIndex {
            arr.append(fn[i](value[i]))
        }
    }
    return arr
}
print(pure(10)) // Output: [10]

var arr = [{ $0 * 2 }, { $0 + 10 }, { $0 - 5 }] < * > [1.2.3]
print(arr) // Output: [2, 12, -2]
Copy the code

List 5.2.

Any type F is said to be a Monad if it supports the following operations.

func pure<A> (_ value: A) -> F<A>
func flatMap<A.B> (_ value: F<A>, _ fn: (A) - >F<B>) -> F<B>
Copy the code

Array and Optional are lists.

Note: some sources will say that monads are functors.

References:

  • Adit. IO/posts / 2013 -…

  • www.mokacoding.com/blog/functo…

For more articles in this series, please pay attention to wechat official account [1024 Planet].