Swift isn't a functional programming language, but it does have a number of functional features. The Combine framework relies on these functional features so it is important that you understand the basics of functional programming. That is the focus of this and the next episode.
Don't let yourself be thrown off by the term functional programming. The concepts we cover are not difficult to grasp. If you want to take advantage of the power Combine offers, you need to become familiar with these concepts.
Before we explore several functional programming concepts, you need to become familiar with the basics, function types and first-class functions. Let's start with function types.
What Are Function Types?
Each function in Swift has a type. When you hear the term type, you probably think of classes, structs, and enums. Classes, structs, and enums are named types. This makes sense because you can refer to the type by name. In this example, we define a class with name ViewController
. We can use the name ViewController
to refer to the type hence the term named type.
class ViewController: UIViewController {
}
Swift also defines types that don't have a name. Functions and tuples are examples of types that don't have a name. We refer to these types as compound types. As the name suggests, a compound type is composed of other types.
The ViewController
class defines a function with name viewDidAppear(_:)
. What is the type of this function? The function has a compound type. It accepts one parameter of type Bool
and it returns Void
. That is the type of viewDidAppear(_:)
.
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
}
}
What Is the Difference Between a Function and a Method?
You may be wondering why I refer to viewDidAppear(_:)
as a function. Isn't viewDidAppear(_:)
a method? That is correct, but every method is a function. A method is nothing more than a function that is associated with a type. In this example, viewDidAppear(_:)
is associated with the ViewController
class. Every method is a function, but not every function is a method.
What Are First-Class Functions?
You now know that functions have a type and that functions, like tuples, have a compound type. Let's now explore the concept of first-class functions. Swift has first-class functions, which means that:
- functions can be stored in constants and variables
- functions can be passed as arguments to other functions
- functions can be returned by other functions
Functions are first-class citizens in Swift, which opens up a lot of possibilities. Let me show you what that means.
Storing Functions in Variables and Constants
Because functions are first-class citizens in Swift, it is possible to store a function in a variable or constant. Let's take a look at an example. The SessionManager
class declares a property with name didSignOut
. The property is of an optional type. The type of the property is a function that accepts no arguments and returns Void
.
The destroySession()
method invokes the function the didSignOut
property references. It uses optional chaining to safely access the value of the didSignOut
property.
internal final class SessionManager {
// MARK: - Properties
var didSignOut: (() -> Void)?
// MARK: - Helper Methods
private func destroySession() {
didSignOut?()
}
}
In this example, we set the value of the didSignOut
property of a SessionManager
instance by assigning a closure to it.
// Configure Session Manager
sessionManager.didSignOut = { [weak self] in
self?.showSignInView()
}
This is starting to get confusing. You are probably wondering by now what the difference is between a closure and a function? Don't worry. The difference is quite simple.
A closure is a function. We use the term closure if the function captures constants and variables from the surrounding context in which it is defined. The closure we assign to the didSignOut
property of the session manager, captures a reference to self
. That is why we use the term closure.
Passing a Function As an Argument
We sometimes take it for granted how straightforward it is to perform animations using the UIView
class. This example should look familiar. We call animate(withDuration:animations:)
on UIView
to animate the alpha
property of likeButton
.
// Hide Like Button
UIView.animate(withDuration: 1.0) {
self.likeButton.alpha = 0.0
}
The first argument is the duration of the animation and the second argument is a closure. Passing a closure as the second argument of the animate(withDuration:animations:)
method is only possible because functions are first-class citizens in Swift. If this sounds confusing, then remember that closures are functions.
Returning a Function From a Function
Returning a function from a function may sound odd and it sort of is if you consider the syntax. Let's take a look at an example. Don't let yourself be thrown off by the syntax. This is without a doubt the most complex code snippet of this episode. Bear with me. Let's break the function definition up into smaller pieces that make more sense.
func increment(by increment: Int) -> (Int) -> Int {
{ (value) in value + increment }
}
We define a function increment(by:)
that accepts an argument of type Int
. The return type of the increment(by:)
function is a function type. The function that is returned accepts an argument of type Int
and returns a value of type Int
. That is why the return arrow appears twice in the function definition. We can make this more explicit by wrapping the return type in parentheses.
func increment(by increment: Int) -> ((Int) -> Int) {
{ (value) in value + increment }
}
The body of the increment(by:)
function isn't that complex. We create a closure that accepts one argument and returns a value of type Int
. We could make this more explicit like this.
func increment(by increment: Int) -> ((Int) -> Int) {
{ (value) -> Int in
value + increment
}
}
Let's put the increment(by:)
function to use. We invoke the increment(by:)
function, passing it a value, 5
. The function the increment(by:)
function returns is stored in a constant with name incrementByFive
.
func increment(by increment: Int) -> ((Int) -> Int) {
{ (value) -> Int in
value + increment
}
}
let incrementByFive = increment(by: 5)
As we explored earlier, first-class functions can be stored in variables and constants. That is what is happening here. The incrementByFive
constant holds a reference to a function. This means that we can call the function stored in incrementByFive
.
incrementByFive(2) // 7
What's Next?
Functional programming can be confusing if you are not familiar with its concepts. Don't worry, though. We take it one step at a time. In the next episode, we cover pure functions and higher-order functions.