Home Functions Types
Post
Cancel

Functions Types

We’ve talked a lot about Functions in the previous articles. Today, we’re going to talk about Function Types! Every Swift function has a type that consists of the input parameter’s type and the output return type.

It’s a simple but very important concept in Swift programming, that you should master to become a great Swift developer. To better understand this topic let’s start with a simple example and then add complexity step by step.

The code below is a super simple “Hello, World!” function.

1
2
3
func helloWorld () {
	print("Hello, World!")
}

It doesn’t need an input to run. The function prints “Hello, World!” to the console, and doesn’t return any type.

What is the type of this function?

The type for this function is:

1
() -> Void

Let’s disassemble this function’s type:

INPUT: () The function takes no paramethers, so the input paramether is just an open and closed parentheses

ARROW SIGN: -> The arrow sign precedes the return type.

OUTPUT: Void The function doesn’t have a return type

Void is the return type of functions that don’t explicitly specify a return type.

In Swift Void is defined as an empty tuple ()

You can use the type(of:) Generic Function to know what is a function’s type.

Let’s do it with the helloWorld function shown below

1
2
3
4
5
6
7
8
9
10
func helloWorld() {
    print("Hello, World!")
}

// Use the type(of:) function by passing it the function's name.
// The function's name, in this case, is "helloWorld"

// Now, print the function's type
print(type(of: helloWorld)) 
// The console prints: () -> ()

As stated above, () as a return type is an empty tuple and means Void, the absence of a return value.

Function’s type with parameters and return value

Now, let’s add some complexity and write a function named greet(person:) that takes a person’s name as parameter and returns a greeting for that person.

1
2
3
func greet(person: String) -> String {
	return "Hi, \(person)"
}

The above function has a type of (String) -> String because takes a parameter of type String and returns a String.

It’s that simple!

And now a question may arise: why is so important to know a function’s type? Because:

  1. It’s possibile to pass a function as a parameter to another function
  2. A function can return another function

These two points open a lot of possibilities…

Let’s see, in order, how we can pass a function’s type as parameter and how we can return a function’s type from a function.

Function Types as Parameter Types

If you’ve understood how a function type is defined, than it should be pretty simple to understand how you can pass the function type to another function.

Let’s see it with an example.

We want to write a function named filterEvenNumbers that takes an array of numbers that we want to filter and returns an array of even numbers, if present, or an empty array otherwise.

To know if a number is even we could use the modulo operator % .

The modulo operator returns the remainder of a division. In our case, a remainder of a division by two. So if the remainder of a division by two is zero, than the number is even.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Write the filterEvenNumbers(numbers:) function
func filterEvenNumbers(numbers: [Int]) -> [Int] {
    var filteredNumbers = [Int]()
    
    for num in numbers {
        if num % 2 == 0 {
            filteredNumbers.append(num)
        }
    }
    
    return filteredNumbers
}

// Define an array of numbers
let someNumbers = [56, 44, 71, 89, 332, 1235]

// Call the the filterEvenNumbers(numbers:) function with the above array and 
// print the result

print(filterEvenNumbers(numbers: someNumbers)) // Prints [56, 44, 332]

Now, let’s say we want to write a function named filterOddNumbers that takes an array of numbers that we want to filter and returns an array of odd numbers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Write the filterOddNumbers(numbers:) function
func filterOddNumbers(numbers: [Int]) -> [Int] {
    var filteredNumbers = [Int]()
    
    for num in numbers {
        if num % 2 != 0 {
            filteredNumbers.append(num)
        }
    }
    
    return filteredNumbers
}

// Define an array of numbers
let someNumbers = [56, 44, 71, 89, 332, 1235]

// Call the the filterOddNumbers(numbers:) function with the above array and 
// print the result

print(filterOddNumbers(numbers: someNumbers)) // Prints [71, 89, 1235]

We should notice that the code of the function filterOddNumbers(numbers:) is very similar to the code of the filterEvenNumbers(numbers:) function. The only thing that changes is the condition that we use to filter the numbers. In the case of the even function, as we said, a number % 2 == 0, while in the case of the odd function a number % 2 ≠ 0.

When you duplicate some code and write functions that share a lot of line of code, there’s a good chance that you can refactor your code and write it in a better way. So, this is not the smartest way to write this type of functions.

But, how can we improve our code? Of course, by passing a function as parameter! The task remain the same, we want to write a function that filters some numbers.

We can write it in a more general way by writing a function that takes two parameter:

  • an array of numbers to filter of type [Int]
  • a function that include the condition to met to filter the array, of type (Int) → Bool

And, as before, returns the array filtered or an empty array.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// This is the function named filter, that accepts:
// numbers (An array of Integers)
// condition (A function that takes an Integer and return a Bool that is
// true if the condition has met, false otherwise)
func filter(numbers: [Int], condition: (Int) -> Bool) -> [Int] {
    
    var filteredNumbers = [Int]()
    
    for num in numbers {
        
        if condition(num) {
            filteredNumbers.append(num)
        }
        
    }
    
    return filteredNumbers
}

Now, how can we use the above function to filter an array and get only even numbers? We need a function that implement this condition.

1
2
3
4
// This function implements the condition for even numbers
func evenNumber(_ number: Int) -> Bool {
    number % 2 == 0
}

The evenNumber(_:) function is compliant with the parameter condition defined in the filter function. The parameter and the evenNumber(_:) function, both have a type of (Int) → Bool.

This means that the evenNumber(_:) is a valid condition that we can pass to the filter(numbers:condition:) function!

At last, we should simply:

1
2
3
4
5
//Define an array of numbers
let someNumbers = [56, 44, 71, 89, 332, 1235]

// call the filter functions passing the numbers and the evenNumber function's name
print(filter(numbers: someNumbers, with: evenNumber)) // Prints [56, 44, 332]

And now, if we want to filter the numbers to get odd numbers only, we should simply define a oddNumber function, that respect the imposed type (Int) → Bool and that implement the condition for Odd numbers.

1
2
3
4
// This function implements the condition for odd numbers
func oddNumber(_ number: Int) -> Bool {
    number % 2 != 0
}

And now, just like before we can:

1
2
3
4
5
//Define an array of numbers
let someNumbers = [56, 44, 71, 89, 332, 1235]

// call the filter functions passing the numbers and the oddNumber function's name
print(filter(numbers: someNumbers, with: oddNumber)) // Prints [71, 89, 1235]

Can you see how powerful is this approach in comparison with the first one? At start we had to write a different function for every condition we wanted to met. Right now, we’ve defined a more generic function that filters array based on a condition that is the only thing we have to change. Open your Playground and test this solution, by writing different functions with other conditions (is a prime number, is greater than 10…)

Function Types as Return Types

What is a Function’s Type now should be clear.

So, it should be not surprising that a Function’s Type can be a Return Type.

As always, let’s see it with an example.

We want to write a function that takes a parameter of type String, used to perform some simple math operations. The parameter could be a String + to indicate that we want to perform a sum, a String - indicating that we want perform a subtraction, or any other string, to indicate that we want to get as result the lowest number. I agree, this is not a super smart function, for a calculator, but it can be good for the purpose of this tutorial. The return parameter of this function should be a function’s type.

First of all, let’s write this function:

1
2
3
4
5
6
7
8
9
10
11
func mathOperation (_ op: String) -> (Int, Int) -> Int {
    
    if op == "+" {
        return sum
    } else if op == "-" {
        return subtraction
    }
    
    return theLower
    
}

The mathOperation(_:) function takes a String and return a function defined as (Int, Int) → Int.

Into the body of the function we check the op string and return:

  • sum if op == “+”
  • subtraction if op == “-”
  • or theLower as a default in other cases.

But what are those names? It’s simple: are functions, defined like that:

1
2
3
4
5
6
7
8
9
10
11
func sum(_ a: Int, _ b: Int) -> Int {
    a + b
}

func subtraction(_ a: Int, _ b: Int) -> Int {
    a - b
}

func theLower(_ a: Int, _ b: Int) -> Int {
    a < b ? a : b
}

The above three functions are compliant with the type (Int, Int) → Int, so can be used as return type of the mathOperation(_:) function. Now we can define a constant to store that function. Call the mathOperation function by passing + and store the result in mathOp constant. What do you think that is the type of mathOp???

It’s simply (Int, Int) → Int

The return type of the three function defined above.

1
2
// We can omit the function's type, 'cause Swift is smart enough to infer it.
let mathOp = mathOperation("+")
1
2
3
4
5
6
7
8
// If you want to explicitly set the type of the constant
// you could write it like shown below
// It's exactly the same as the above mathOp constant. 
// Both have type (Int, Int) -> Int
let mathOp : (Int, Int) -> Int = mathOperation("+")

// Now, use mathOp, to perform a sum on two integers and print the result
print(mathOp(10, 6)) // prints 16

Now, perform a subtraction

1
2
3
4
let mathOp = mathOperation("-")

// Use mathOp, to perform a subtraction on two integers and print the result
print(mathOp(10, 6)) // prints 4

And, finally pass an empty string to get the lowest number

1
2
3
4
let mathOp = mathOperation("")

// Use mathOp, to get the lowest number and print the result
print(mathOp(10, 6)) // prints 6

I understand that it might be confusing, at first. But, trust me, with a little bit of practice you will understand perfectly what is going on.

Nested Functions

All the functions that we’ve seen so far are global functions, which are defined at a global scope. But we can nest functions inside other functions. Nested functions are hidden from the global scope, but can be called and used by their enclosing functions. Let’s change the example used before a little bit, by nesting the sum, subtraction and theLower functions inside the mathOperation function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func mathOperation (_ op: String) -> (Int, Int) -> Int {

		func sum(_ a: Int, _ b: Int) -> Int {
	    a + b
		}

		func subtraction(_ a: Int, _ b: Int) -> Int {
		   a - b
		}

		func theLower(_ a: Int, _ b: Int) -> Int {
	    a < b ? a : b
		}
    
    if op == "+" {
        return sum
    } else if op == "-" {
        return subtraction
    }
    
    return theLower
    
}

Now, the three functions are inside the mathOperation! We can’t call them from the global scope. We can only call the mathOperation exactly like we did before, nothing changes. The only thing that changes is that before the three functions were outside the mathOperation function and now are nested inside the mathOperation and we can’t call and use them in the global scope.

This post is licensed under CC BY 4.0 by the author.
Trending Tags
Contents
Trending Tags