Function composition

In the previous section, we have seen an example of higher-order functions that could accept two different functions and execute them in a predefined order. This function was not so flexible in the sense that it would break if we wanted to combine two accepted functions differently. Function composition can solve this issue and make it even more flexible. To present this concept, we will examine an example of non-functional composition first, and then we will be introduced to functional composition.

Suppose that, in our application, we need to interact with a backend RESTful API and receive a String value that contains a list of prices in order. The backend RESTful API is being developed by a third-party and is not designed properly. Unfortunately, it returns a String with numbers in it separated by commas:

"10,20,40,30,80,60" 

We need to format the content that we are receiving before using it. We will extract elements from String and create an array, and then we will append $ as currency to each item to use it in a tableview. The following code example presents an approach to this problem:

let content = "10,20,40,30,80,60"

func extractElements(_ content: String) -> [String] {
    return content.characters.split(separator: ",").map { String($0) }
}

let elements = extractElements(content)

func formatWithCurrency(content: [String]) -> [String] {
    return content.map {"($0)$"}
}

let formattedElements = formatWithCurrency(content: elements)

In this code example, we treated each function individually. We could use the result of the first function as an input parameter for the second function. Either approach is verbose and not functional. Additionally, we use the map function, which is a higher-order function, but our approach is still not functional.

Let's approach this problem in a functional way.

The first step will be to identify function types for each function:

  • extractElements: String -> [String]
  • formatWithCurrency: [String] -> [String]

If we pipe these functions, we will get the following:

extractElements: String -> [String] | formatWithCurrency: [String] 
  -> [String]

We can combine these functions with a functional composition and the composed function will be of the String -> [String] type. The following example shows the composition:

let composedFunction = { data in
    formatWithCurrency(content: extractElements(data))
}

composedFunction(content)

In this example, we define composedFunction, which is composed of two other functions. We are able to compose functions like this as each function has at least one parameter and return value. This composition is like the mathematical composition of functions. Suppose that we have a function f(x) that returns y and a g(y) function that returns z. We can compose the g function as g(f(x)) -> z. This composition makes our g function take x as a parameter and return z as a result. This is exactly what we have done in our composedFunction.

Custom operators

Although composedFunction is less verbose than the non-functional version, it does not look great. Also, it is not easy to read as we need to read it inside out. Let's make this function simpler and more readable. One solution will be to define a custom operator that will be used instead of our composed function. In the following sections, we will examine what are the standard operators that are allowed to define a custom operator. We will also explore the custom operator definition technique. It is important to learn this concept as we will be using it in the rest of the book.

Allowed operators

The Swift standard library provides a number of operators that can be used to define custom operators. Custom operators can begin with one of the ASCII characters—/, =, -, +, !, *, %, <, >, &, |, ^, ?, or ~ or one of the Unicode characters. After the first character, combining Unicode characters is allowed.

We can also define custom operators that begin with a dot. If an operator does not start with a dot, it cannot contain a dot elsewhere. Although we can define custom operators that contain a question mark ?, they cannot consist of a single question mark character only. Additionally, although operators can contain an exclamation point !, postfix operators cannot begin with either a question mark or exclamation point.

Custom operator definition

We can define custom operators using the following syntax:

operatorType operator operatorName { } 

Here, operatorType can be one of the following:

  • prefix
  • infix
  • postfix

Custom infix operators can also specify a precedence and an associativity:

infix operator operatorName { associativity left/right/none 
  precedence}

The possible values for associativity are left, right, and none. Left-associative operators associate to the left if written next to other left-associative operators of the same precedence. Similarly, right-associative operators associate to the right if written next to other right-associative operators of the same precedence. Non-associative operators cannot be written next to other operators with the same precedence.

The associativity value defaults to none if it is not specified. The precedence value defaults to 100 if it is not specified.

Any custom operator defined with the preceding syntax will not have an existing meaning in Swift; therefore, a function with operatorName as its name should be defined and implemented. In the following section, we will examine an example of custom operator definition with its respective function definition.

A composed function with a custom operator

Let's define a new custom operator to use instead of our composed function:

infix operator |> { associativity left }
func |> <T, V>(f: T -> V, g: V -> V ) -> T -> V {
    return { x in g(f(x)) }
}

let composedWithCustomOperator = extractElements |> formatWithCurrency
composedWithCustomOperator("10,20,40,30,80,60")

In this example, we have defined a new operator, |>, that takes two generic functions and combines them, returning a function that has the first function's input as the parameter and the second function's return as the return type.

As this new operator is going to combine two functions and is binary, we defined it as infix. Then we need to use the operator keyword. The next step will be to choose the notation for our new custom operator. As we will group functions to the left, we need to specify it as associativity left.

To be able to use this operator, we need to define a corresponding function. Our function takes two functions as follows:

  • f: This function takes a generic type of T and returns a generic type of V
  • g: This function takes a generic type of V and returns a generic type of V

In our example, we had the following functions:

  • extractElements: String -> [String]
  • formatWithCurrency: [String] -> [String]

So T becomes String and V becomes [String].

Our |> function returns a function that takes a generic type of T and returns a generic type of V. We need to receive String -> [String] from the composed function so, again, T becomes String and V becomes [String].

Using our |> custom operator makes our code more readable and less verbose.

..................Content has been hidden....................

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