In the previous article, we explored the basics of functions in Swift. Functions, however, have a lot more to offer. In this article, we will continue our exploration of functions and look into function parameters, nesting, and types.
1. Local & External Parameter Names
Local Parameter Names
Let's revisit one of the examples of the previous article. The printMessage
function defines one parameter, message
.
func printMessage(message: String) { println(message) }
Even though we give the parameter a name, message
, we don't use the name when we call the function. We pass in the value for the message
parameter.
printMessage("Hello, world!")
The name we define in the function's definition is a local parameter name. The value of the parameter can only be referenced by this name in the body of the function. Function parameters, however, are a bit more flexible than that. Let me explain what I mean by that.
Objective-C is known for its long method names. While this may look clunky and inelegant to outsiders, it makes methods easy to understand and, if chosen well, very descriptive. If you think you lost this benefit when switching to Swift, then you're in for a surprise.
External Parameter Names
When a function accepts several parameters, it isn't always obvious which argument corresponds to which parameter. Take a look at the following example to better understand the problem.
func power(a: Int, b: Int) -> Int { var result = a for _ in 1..<b { result = result * a } return result }
The power
function raises the value of a
by the exponent b
. Both parameters are of type Int
. While most people will intuitively pass the base value as the first argument and the exponent as the second argument, this isn't clear from the function's type, name, or signature. As we've seen in the previous article, invoking the function is straightforward.
power(2, 3)
To avoid confusion, we can give the parameters of a function external names. We can then use these external names when the function is called to unambiguously indicate which argument corresponds to which parameter. Take a look at the updated example below.
func power(base a: Int, exponent b: Int) -> Int { var result = a for _ in 1..<b { result = result * a } return result }
Note that the function's body hasn't changed since the local names haven't changed. However, when we invoke the updated function, the difference is clear and the result much less confusing.
power(base: 2, exponent: 3)
While the types of both functions are the same, (Int, Int) -> Int
, the functions are different. In other words, the second function isn't a redeclaration of the first function. The syntax to invoke the second function may remind some of you of Objective-C. Not only are the arguments clearly described, the combination of function and parameter names describe the purpose of the function.
In some cases, you want to use the same names for local and external parameter names. This is possible and there's no need to type the parameter name twice. In the following example, we use base
and exponent
as the local and external parameter names.
func power(#base: Int, #exponent: Int) -> Int { var result = base for _ in 1..<exponent { result = result * base } return result }
By prefixing the parameter name with a #
symbol, the parameter name serves as the local and external name of the parameter. This also means that we need to update the body of the function.
It's important to note that by providing an external name for a parameter you are required to use that name when invoking the function. This brings us to default values.
Default Values
We covered default parameter values in the previous article, but there is an important default behavior that you need to be aware of. If you define a default value for a parameter, Swift automatically assigns an external parameter name to the parameter. Let's look at an example of the previous article.
func printDate(date: NSDate, format: String = "YY/MM/dd") -> String { let dateFormatter = NSDateFormatter() dateFormatter.dateFormat = format return dateFormatter.stringFromDate(date) }
Because the second parameter, format
, has a default value, Swift automatically sets the external parameter name of format
to format
. In other words, the result is the same as if we were to prefix format
with a #
symbol. You can test this by invoking the above function in your playground. What happens if you pass in the format without using the external parameter name of the second parameter? The answer is shown below.
Swift is pretty clear about what we should do. To summarize, when you define a parameter as optional, Swift automatically sets the external parameter name to the local parameter name. The idea behind this behavior is avoiding confusion and ambiguity.
While this behavior is a good best practice, it is possible to disable it. In the updated function definition below, we add an underscore where we would normally add the external parameter name. This tells Swift that we don't want to set an external parameter name for that particular parameter, despite having a default value.
func formatDate(date: NSDate, _ format: String = "YY/MM/dd") -> String { let dateFormatter = NSDateFormatter() dateFormatter.dateFormat = format return dateFormatter.stringFromDate(date) }
We can now invoke the formatDate
function without providing a name for the second argument.
formatDate(NSDate(), "dd/MM/YY")
2. Parameters & Mutability
Let's revisit the first example of this tutorial, the printMessage
function. What happens if we change the value of the message
parameter inside the function's body?
func printMessage(message: String) { message = "Print: \(message)" println(message) }
It doesn't take long for Swift to start complaining.
By default, the parameters of a function are constants. In other words, while we can access the values of function parameters, we cannot change their value. To change this default behavior, add the var
keyword to the parameter name in the function definition. Swift will then create a variable copy of the parameter's value for you to work with in the function's body.
func printMessage(var message: String) { message = "Print: \(message)" println(message) }
Note that this doesn't mean that you can pass in a value, modify it in the function, and use the modified value after the function has done its work. Swift creates a copy of the parameter's value that only exists for the lifetime of the function call. This is also illustrated in the following code block in which we pass a constant to the printMessage
function.
func printMessage(var message: String) { message = "Print: \(message)" println(message) } let myMessage = "test" printMessage(myMessage)
3. Variadic Parameters
While the term may sound odd at first, variadic parameters are common in programming. A variadic parameter is a parameter that accepts zero or more values. The values need to be of the same type. Using variadic parameters in Swift is trivial as the following example illustrates.
func sum(args: Int...) -> Int { var result = 0 for a in args { result += a } return result }
The syntax is easy to understand. To mark a parameter as variadic, you append three dots to the parameter's type. In the function body, the variadic parameter is accessible as an array. In the above example, args
is an array of Int
values.
Because Swift needs to know which arguments correspond to which parameters, a variadic parameter is required to be the last parameter. It also implies that a function can have at most one variadic parameter.
The above also applies if a function has parameters with default values. The variadic parameter should always be the last parameter.
4. In-Out Parameters
Earlier in this tutorial, you learned how you can define the mutability of a parameter by using the var
keyword. In that section, I emphasized that the value of a variable parameter is only accessible from within the function body. If you want to pass a value into a function, modify it in the function, and pass it back out of the function, in-out parameters are what you're looking for.
The following example shows an example of how in-out parameters work in Swift and what the syntax looks like.
func prependString(inout a: String, withString b: String) { a = b + a }
We've defined the first parameter as an in-out parameter by adding the inout
keyword. The second parameter is a regular parameter with an external name of withString
and a local name of b
. Let's see how we invoke this function.
var world = "world" prependString(&world, withString: "Hello, ")
We declare a variable, world
, of type String
and pass it to the perpendString
function. The second parameter is a string literal. By invoking the function, the value of the world
variable becomes Hello, world
. Note that the first argument is prefixed with an ampersand, &
, to indicate that it's an in-out parameter.
It goes without saying that constants and literals cannot be passed in as in-out parameters. Swift will thrown an error when you do as illustrated in the following screenshot.
It's evident that in-out parameters cannot have default values, be variadic, or be defined as var
or let
. If you forget these details, Swift will kindly remind you with an error.
5. Nesting
In C and Objective-C, functions and methods cannot be nested. In Swift, however, nested functions are quite common. The functions we've seen in this and the previous article are examples of global functions, they are defined in the global scope.
When we define a function inside a global function, we refer to that function as a nested function. A nested function has access to the values defined in its enclosing function. Take a look at the following example to better understand this.
func printMessage(message: String) { let a = "hello world" func printHelloWorld() { println(a) } }
While the functions in this example aren't terribly useful, the example illustrates the idea of nested function and capturing values. The printHelloWorld
function is only accessible from within the printMessage
function. As illustrated in the example, the printHelloWorld
function has access to the constant a
. The value is captured by the nested function and is therefore accessible from within that function. Swift takes care of capturing values, including managing the memory of those values.
6. Function Types
In the previous article, we briefly touched upon function types. A function has a particular type, composed of the function's parameter types and its return type. The printMessage
function, for example, is of type (String) -> ()
. Remember that ()
symbolizes Void
, which is equivalent to an empty tuple.
Because every function has a type, it's possible to define a function that accepts another function as a parameter. The following example shows how this works.
func printMessageWithFunction(message: String, printFunction: (String) -> ()) { printFunction(message) } let myMessage = "Hello, world!" printMessageWithFunction(myMessage, printMessage)
The printMessageWithFunction
function accepts a string as its first parameter and a function of type (String) -> ()
as its second parameter. In the function's body, the function that we pass in is invoked with the message
argument.
The example also illustrates how we can invoke the printMessageWithFunction
function. The myMessage
constant is passed in as the first argument and the printMessage
function, which we defined earlier, as the second argument. How cool is that?
As I mentioned earlier, it's also possible to return a function from a function. The next example is a bit contrived, but it illustrates what the syntax looks like.
func compute(addition: Bool) -> (Int, Int) -> Int { func add(a: Int, b: Int) -> Int { return a + b } func subtract(a: Int, b: Int) -> Int { return a - b } if addition { return add } else { return subtract } } let computeFunction = compute(true) let result = computeFunction(1, 2) println(result)
The compute
function accepts a boolean and returns a function of type (Int, Int) -> Int
. The compute
function contains two nested functions that are also of type (Int, Int) -> Int
, add
and subtract
.
The compute
function returns a reference to either the add
or the subtract
function, based on the value of the addition
parameter. The example also shows how to use the compute
function. We store a reference to the function that is returned by the compute
function in the computeFunction
constant. We then invoke the function stored in computeFunction
, passing in 1
and 2
, store the result in result
, and print the value of result
in the standard output. The example may look complex, but it's actually easy to understand if you know what's going on.
Conclusion
You should now have a good understanding of how functions work in Swift and what you can do with them. Functions are a common theme in Swift and you will use them extensively when working with Swift.
In the next article, we dive head first into closures, a powerful construction that is very reminiscent of blocks in C and Objective-C, closures in JavaScript, and lambdas in Ruby.