Categories: ProgrammingTutorials

Functions in Swift

14 min read

In this article by Dr. Fatih Nayebi, the author of the book Swift 3 Functional Programming, we will see that as functions are the fundamental building blocks in functional programming, this article dives deeper into it and explains all the aspects related to the definition and usage of functions in functional Swift with coding examples.

This article will cover the following topics with coding examples:

  • The general syntax of functions
  • Defining and using function parameters
  • Setting internal and external parameters
  • Setting default parameter values
  • Defining and using variadic functions
  • Returning values from functions
  • Defining and using nested functions

(For more resources related to this topic, see here.)

What is a function?

Object-oriented programming (OOP) looks very natural to most developers as it simulates a real-life situation of classes or, in other words, blueprints and their instances, but it brought a lot of complexities and problems such as instance and memory management, complex multithreading, and concurrency programming.

Before OOP became mainstream, we were used to developing in procedural languages. In the C programming language, we did not have objects and classes; we would use structs and function pointers. So now we are talking about functional programming that relies mostly on functions just as procedural languages relied on procedures. We are able to develop very powerful programs in C without classes; in fact, most operating systems are developed in C. There are other multipurpose programming languages such as Go by Google that is not object-oriented and is getting very popular because of its performance and simplicity.

So, are we going to be able to write very complex applications without classes in Swift? We might wonder why we should do this. Generally, we should not, but attempting it will introduce us to the capabilities of functional programming.

A function is a block of code that executes a specific task, can be stored, can persist data, and can be passed around. We define them in standalone Swift files as global functions or inside other building blocks such as classes, structs, enums, and protocols as methods.

They are called methods if they are defined in classes but in terms of definition, there is no difference between a function and method in Swift.

Defining them in other building blocks enables methods to use the scope of the parent or to be able to change them. They can access the scope of their parent and they have their own scope. Any variable that is defined inside a function is not accessible outside of it. The variables defined inside them and the corresponding allocated memory goes away when the function terminates.

Functions are very powerful in Swift. We can compose a program with only functions as functions can receive and return functions, capture variables that exist in the context they were declared, and can persist data inside themselves. To understand the functional programming paradigms, we need to understand the capability of functions in detail. We need to think if we can avoid classes and only use functions so we will cover all the details related to functions in the upcoming sections of this article.

The general syntax of functions and methods

We can define functions or methods as follows:

accessControl func functionName(parameter: ParameterType) throws 
  -> ReturnType { }

As we know already, when functions are defined in objects, they become methods.

The first step to define a method is to tell the compiler from where it can be accessed. This concept is called access control in Swift and there are three levels of access control. We are going to explain them for methods as follows:

  • Public access: Any entity can access a method that is defined as public if it is in the same module. If an entity is not in the same module, we will need to import the module to be able to call the method. We need to mark our methods and objects as public when we develop frameworks in order to enable other modules to use them.
  • Internal access: Any method that is defined as internal can be accessed from other entities in a module but cannot be accessed from other modules.
  • Private access: Any method that is defined as private can be accessed only from the same source file.

By default, if we do not provide the access modifier, a variable or function becomes internal.

Using these access modifiers, we can structure our code properly, for instance, we can hide details from other modules if we define an entity as internal. We can even hide the details of a method from other files if we define them as private.

Before Swift 2.0, we had to define everything as public or add all source files to the testing target. Swift 2.0 introduced the @testable import syntax that enables us to define internal or private methods that can be accessed from testing modules.

Methods can generally be in three forms:

  • Instance methods: We need to obtain an instance of an object (In this article we will refer to classes, structs, and enums as objects) in order to be able to call the method defined in it, and then we will be able to access the scope and data of the object.
  • Static methods: Swift names them type methods also. They do not need any instances of objects and they cannot access the instance data. They are called by putting a dot after the name of the object type (for example, Person.sayHi()). The static methods cannot be overridden by the subclasses of the object that they reside in.
  • Class methods: Class methods are like the static methods but they can be overridden by subclasses.

We have covered the keywords that are required for method definitions; now we will concentrate on the syntax that is shared among functions and methods. There are other concepts related to methods that are out of scope of this article as we will concentrate on functional programming in Swift.

Continuing to cover the function definition, now comes the func keyword that is mandatory and is used to tell the compiler that it is going to deal with a function.

Then comes the function name that is mandatory and is recommended to be camel-cased with the first letter as lowercase. The function name should be stating what the function does and is recommended to be in the form of a verb when we define our methods in objects.

Basically, our classes will be named nouns and methods will be verbs that are in the form of orders to the class. In pure functional programming, as the function does not reside in other objects, they can be named by their functionalities.

Parameters follow the func name. They will be defined in parentheses to pass arguments to the function. Parentheses are mandatory even if we do not have any parameters. We will cover all aspects of parameters in an upcoming section of this article.

Then comes throws, which is not mandatory. A function or method that is marked with the throw keyword may or may not throw errors. At this point, it is enough to know what they are when we see them in a function or method signature.

The next entity in a function type declaration is the return type. If a function is not void, the return type will come after the -> sign. The return type indicates the type of entity that is going to be returned from a function.

We will cover return types in detail in an upcoming section in this article, so now we can move on to the last piece of function that is present in most programming languages, our beloved { }. We defined functions as blocks of functionality and {} defines the borders of the block so that the function body is declared and execution happens in there. We will write the functionality inside {}.

Best practices in function definition

There are proven best practices for function and method definition provided by amazing software engineering resources, such as Clean Code, Code Complete, and Coding Horror, that we can summarize as follows:

  • Try not to exceed 8-10 lines of code in each function as shorter functions or methods are easier to read, understand, and maintain.
  • Keep the number of parameters minimal because the more parameters a function has, the more complex it is.
  • Functions should have at least one parameter and one return value.
  • Avoid using type names in function names as it is going to be redundant.
  • Aim for one and only one functionality in a function.
  • Name a function or method in a way that it describes its functionality properly and is easy to understand.
  • Name functions and methods consistently. If we have a connect function, we can have a disconnect one.
  • Write functions to solve the current problem and generalize it when needed. Try to avoid what if scenarios as probably you aren’t going to need it (YAGNI).

Calling functions

We have covered a general syntax to define a function and method if it resides in an object. Now it is time to talk about how we call our defined functions and methods. To call a function, we will use its name and provide its required parameters. There are complexities with providing parameters that we will cover in the upcoming section. For now, we are going to cover the most basic type of parameter providing as follows:

funcName(paramName, secondParam: secondParamName)

This type of function calling should be familiar to Objective-C developers as the first parameter name is not named and the rest are named.

To call a method, we need to use the dot notation provided by Swift. The following examples are for class instance methods and static class methods:

let someClassInstance = SomeClass()
someClassInstance.funcName(paramName, secondParam: 
  secondParamName)
StaticClass.funcName(paramName, secondParam: secondParamName)

 

Defining and using function parameters

In function definition, parameters follow the function name and they are constants by default so we will not able to alter them inside the function body if we do not mark them with var. In functional programming, we avoid mutability, therefore, we would never use mutable parameters in functions.

Parameters should be inside parentheses. If we do not have any parameters, we simply put open and close parentheses without any characters between them:

func functionName() { }

In functional programming, it is important to have functions that have at least one parameter. We will explain why it is important in upcoming sections.

We can have multiple parameters separated by commas. In Swift, parameters are named so we need to provide the parameter name and type after putting a colon, as shown in the following example:

func functionName(parameter: ParameterType, secondParameter: 
  ParameterType) { }
// To call:
functionName(parameter, secondParameter: secondParam)

ParameterType can also be an optional type so the function becomes the following if our parameters need to be optionals:

func functionName(parameter: ParameterType?, secondParameter: 
  ParameterType?) { }

Swift enables us to provide external parameter names that will be used when functions are called. The following example presents the syntax:

Func functionName(externalParamName localParamName: ParameterType)
// To call:
functionName(externalParamName: parameter)

Only the local parameter name is usable in the function body.

It is possible to omit the parameter names with the _ syntax, for instance, if we do not want to provide any parameter name when the function is called, we can use _ as externalParamName for the second or subsequent parameters.

If we want to have a parameter name for the first parameter name in function calls, we can basically provide the local parameter name as external also. In this article, we are going to use the default function parameter definition.

Parameters can have default values as follows:

func functionName(parameter: Int = 3) {
  print("(parameter) is provided."
}
functionName(5) // prints "5 is provided."
functionName() // prints "3 is provided"

Parameters can be defined as inout to enable function callers obtaining parameters that are going to be changed in the body of a function. As we can use tuples for function returns, it is not recommended to use inout parameters unless we really need them.

We can define function parameters as tuples. For instance, the following example function accepts a tuple of the (Int, Int) type:

func functionWithTupleParam(tupleParam: (Int, Int)) {}

As, under the hood, variables are represented by tuples in Swift, the parameters to a function can also be tuples. For instance, let’s have a simple convert function that takes an array of Int and a multiplier and converts it to a different structure. Let’s not worry about the implementation of this function for now:

let numbers = [3, 5, 9, 10]
func convert(numbers: [Int], multiplier: Int) -> [String] {
  let convertedValues = numbers.enumerate().map { (index, element) in
    return "(index): (element * multiplier)"
  }
  return convertedValues
}

If we use this function as convert(numbers, multiplier: 3), the result is going to be [“0: 9”, “1: 15”, “2: 27”, “3: 30”].

We can call our function with a tuple. Let’s create a tuple and pass it to our function:

let parameters = (numbers, multiplier: 3)
convert(parameters)

The result is identical to our previous function call. However, passing tuples in function calls is deprecated and will be removed in Swift 3.0, so it is not recommended to use them.

We can define higher-order functions that can receive functions as parameters. In the following example, we define funcParam as a function type of (Int, Int) -> Int:

func functionWithFunctionParam(funcParam: (Int, Int)-> Int)

In Swift, parameters can be of a generic type. The following example presents a function that has two generic parameters. In this syntax, any type (for example, T or V) that we put inside <> should be used in parameter definition:

func functionWithGenerics<T, V>(firstParam: T, secondParam)

Defining and using variadic functions

Swift enables us to define functions with variadic parameters. A variadic parameter accepts zero or more values of a specified type. Variadic parameters are similar to array parameters but they are more readable and can only be used as the last parameter in the multiparameter functions.

As variadic parameters can accept zero values, we will need to check whether it is empty.

The following example presents a function with variadic parameters of the String type:

func greet(names: String…) {
  for name in names {
    print("Greetings, (name)")
  }
}
// To call this function
greet("Steve", "Craig") // prints twice
greet("Steve", "Craig", "Johny") // prints three times

Returning values from functions

If we need our function to return a value, tuple, or another function, we can specify it by providing ReturnType after ->. For instance, the following example returns String:

func functionName() -> String { }

Any function that has ReturnType in its definition should have a return keyword with the matching type in its body.

Return types can be optionals in Swift so the function becomes as follows if the return needs to be optional:

func functionName() -> String? { }

Tuples can be used to provide multiple return values. For instance, the following function returns tuple of the (Int, String) type:

func functionName() -> (code: Int, status: String) { }

As we are using parentheses for tuples, we should avoid using parentheses for single return value functions.

Tuple return types can be optional too so the syntax becomes as follows:

func functionName() -> (code: Int, status: String)? { }

This syntax makes the entire tuple optional; if we want to make only status optional, we can define the function as follows:

func functionName() -> (code: Int, status: String?) { }

In Swift, functions can return functions. The following example presents a function with the return type of a function that takes two Int values and returns Int:

func funcName() -> (Int, Int)-> Int {}

If we do not expect a function to return any value, tuple, or function, we simply do not provide ReturnType:

func functionName() { }

We could also explicitly declare it with the Void keyword:

func functionName() { }

In functional programming, it is important to have return types in functions. In other words, it is a good practice to avoid functions that have Void as return types. A function with the Void return type typically is a function that changes another entity in the code; otherwise, why would we need to have a function? OK, we might have wanted to log an expression to the console/log file or write data to a database or file to a filesystem. In these cases, it is also preferable to have a return or feedback related to the success of the operation. As we try to avoid mutability and stateful programming in functional programming, we can assume that our functions will have returns in different forms.

This requirement is in line with mathematical underlying bases of functional programming. In mathematics, a simple function is defined as follows:

y = f(x) or f(x) -> y

Here, f is a function that takes x and returns y. Therefore, a function receives at least one parameter and returns at least a value. In functional programming, following the same paradigm makes reasoning easier, function composition possible, and code more readable.

Summary

This article explained the function definition and usage in detail by giving examples for parameter and return types.

You can also refer the following books on the similar topics:

Resources for Article:


Further resources on this subject:


Packt

Share
Published by
Packt

Recent Posts

Harnessing Tech for Good to Drive Environmental Impact

At Packt, we are always on the lookout for innovative startups that are not only…

2 months ago

Top life hacks for prepping for your IT certification exam

I remember deciding to pursue my first IT certification, the CompTIA A+. I had signed…

3 years ago

Learn Transformers for Natural Language Processing with Denis Rothman

Key takeaways The transformer architecture has proved to be revolutionary in outperforming the classical RNN…

3 years ago

Learning Essential Linux Commands for Navigating the Shell Effectively

Once we learn how to deploy an Ubuntu server, how to manage users, and how…

3 years ago

Clean Coding in Python with Mariano Anaya

Key-takeaways:   Clean code isn’t just a nice thing to have or a luxury in software projects; it's a necessity. If we…

3 years ago

Exploring Forms in Angular – types, benefits and differences   

While developing a web application, or setting dynamic pages and meta tags we need to deal with…

3 years ago