Swift Function Definitions
One of the first things that jumped out to me when I got my first glance at some code written in Apple’s new Swift programming language was the syntax of function definitions.
Functional expressions are one of the most important aspects of language syntax and are one of the things that makes Objective-C such a pain in the ass to read and work with, in my opinion. The design of functions in a programming language can have a huge effect on how logic, language idioms, styles, best practices, and overall design is expressed.
Let's take a look at what Apple delivered with Swift...
The first glimpse of Swift I got was a function definition that looked something like this:
And my first thought was “eeeewwww…what an ugly way to define functions and parameter types”
But my first impression of what the syntax was defining was wrong. Part of the confusion was around the fact that Swift provides tuple return types (more on that below) so initially I thought that
(Int, String) were the function's parameter types when in fact they are the return types:
My initial eeeewwww was replaced by why? Most programming languages declare the return type first and the parameter types second. You can read functions with return types stated on the left of the definition as “I’ll return this when you call me with these”:
Swift inverts this a bit by putting the return type at the end of the definition and says “When you call me with these, I’ll return this”:
In statically typed languages, the first thing you enter when defining a new variable is the type it will represent:
If you are using a function to set the value of your variable your first concern is that the return type of the function matches the type of the variable. You are constantly thinking in types and so having the type defined on the left makes a certain amount of sense:
With type inference in Swift, the type of the variable isn’t of first level concern - you just define a variable and call a function to assign a value. Ideally, the variable and the assigning function will have descriptive names so you have a good idea of what is returned but with type inference, you don’t need to set the type explicitly:
If your function doesn’t have a return value, you can just define the function with no type. Functions with no return type implicitly return
Void but there is no need to define it as such:
Declaring the return types at the end of a function definition is a fairly significant stylistic change but it is perhaps more importantly a rather subtle nod to the effect type inference may have in the way we think about types in the course of programming in Swift.
Multiple Return Types with Tuples
Swift includes support for multiple return types with tuples. The syntax looks something like this:
This is an example of anonymous tuple values…and it’s terrible! What do each of the Doubles represent? What is the String?
Thankfully, there is also support for named tuples which can be used and accessed by name much like C-style structs.
When used judiciously, tuples can be a lightweight and useful way to easily structure related data. On the flip side, they also introduce an un-useful and unfortunate way of “helping” you to mix the concerns of your functions which will result in poorly factored code. If you find yourself jamming things into tuples, you’re more than likely introducing a code smell. Make sure you're only using tuples for temporary data.
Please Tuple responsibly…
External and Named Parameters
Swift has the notion of External & External Short-hand Parameters. If you're familiar with Objective-C this will seem familiar to you. We’ll start with external parameters first:
In this example, the parameter has two names: the external name,
value and the internal name,
v. Within the function body you must refer to the variable by
v and not
value. However, when you call the function you always need to provide the external name for each parameter:
When using short-hand external parameters, the external and internal names are the same. To do this, just prefix the parameter name with a
#. You are still required to use the name when calling the function but you reference the variable by the same name internally and externally.
One of the stated purposes for these types of parameters it to make function calls easier to read. Personally, I think the idea for external named parameters that don't match the function's internal variables names is pretty limited at best. In general, you should choose clear variable names that are used inside the function and out.
Named parameters do have the ability to make for more readable function calls but having internal variable names seems like a way to make your code less readable.
Your mileage may vary.
In addition to named parameters, you can also assign default values in function signatures. Default values can be helpful by requiring less overloading and can encourage Open/Closed designs.
Please note that default arguments always have to be named and that by assigning the default values you are also implicitly declaring instances of local variables of the same name like you would with external short-hand parameters. However, if you write the above function definition with the external short-hand notation like this:
You will get the following warning:
Extraneous # in parameter; default argument implies keyword argument
As a best practice, you should always put your default parameters at the end of your function definitions so that the order doesn’t get mixed up when making calls. For example, this is legal:
But will throw this error if you try to pass in
age as the second parameter to your call:
Argument 'age' must precede argument 'name'
Redefine this function to have the default parameter last and your calls will look more consistent:
You can mix and match the way you define your parameters but be careful: mixing default values with named and unnamed variables will compile but can result in a total mess.
Default values in function definitions are a pretty familiar concept in many interpreted languages like Ruby and Python and are a welcome inclusion to Swift. My hope is that default values will help to avoid excessive overloading that is so common in languages like Java & Objective-C.
Variadic parameters have become pretty common in many modern languages and for good reason: they're pretty handy.
The variadic parameters are defined with the familiar
One thing to be aware of is that chaining calls to functions with variadic parameters may not work as you first expect:
The above will result in:
could not find overload for \__conversion that accepts the supplied arguments
sum function expects a variadic parameter but it will be passed as an
Int. This behavior should not come as a surprise if you've worked in other languages with similar constructs but it is worth pointing out. To work around this you could do:
Each function can only have a single variadic parameter and it must always be defined as the last parameter to the function. Variadic parameters can also be named which can help with readability but they cannot have default value defined in the function signature.
By default, all parameters passed to a function are constants: local copies are not created and the parameters themselves are immutable. This code will fail to compile because the function is attempting to assign a new value to a constant:
You could make a local copy for use in your function like this:
But Swift has a shortcut for defining local copies of parameters that will be used and modified as variables within the scope of your function. You can define it as a
var in the function definition.
In both examples the local variable is only scoped to the execution of the function. If you want to pass a value by reference you need to use
inout. This allows the function to make changes to the values that affect the value of the variable where it was defined.
You do need to make sure that when making calls to functions with
inout parameters you prefix them with
& - failing to do so will cause a compilation error.
I've never been a big fan of this type of programming but I'm glad that the intention for passing by reference is clearly defined by requiring
inout and that the usage of functions with
inout parameters is made clear to the caller by requiring
& for invocations.
Swift function definitions provide a few twists on languages and is a bit of a depature from what we've been using to build iOS and Mac applications to date. I've just started to scratch the surface of Swift but so far I like what I see - certainly more than I like Objective-C. Apple set out to create a new and modern language and while some of what they've delivered is legacy support (
inout and external named parameters) I think they've created an interesting language that has the potential to improve the experience for Mac and iOS software development.
There are some other pretty interesting features related to functions that I've left out here. Specifically, function types, functions as parameters, functions as return types, nested functions & closures. I'll save those for separate blog posts.