Title

Swift Closures: Inline functions explained by a web developer

Hi, I'm Thomas. I'm a frontend engineer who's learning swift. Let's talk about closures in Swift from a very (very) introductory level. I'm assuming you've got some familiarity with JavaScript. You should definitely check out Apple's documentation on Closures, it's so much better than this page but also, like, less funny?

Closures are inline function definitions.

We have inline functions all over the place in JavaScript. Callback functions, including those in promises, are often declared in JavaScript:

// Ever written an express routers?
router.use('/users', (req, res, next) => { .. })

// Ever write a promise?
fetch("https://www.google.com").then((res) => { .. })

Like JavaScript, Swift has functions as a first-class citizen. That means they can be passed around like any other variable. That's pretty cool, and if you come from a pure JS background you might not realise it. You might think it's pretty annoying. But it's not. Try filtering a dataframe into a subset in Python using Pandas without the filter-like function syntax (don't @ me).

We use closures in Swift in exactly the same use case: when we need to pass a function as an argument to a function. For example, if we are passing a custom sort, map, or sorted function on an array.

In the same mental model that React uses, a SwiftUI View is like a React component: it's fundamentally a function: f(state) => ui - UI is a function of state.

Understanding Closures in swift will help you write, and read, SwiftUI examples.

The name's a little confusing. In JavaScript a closure is the scope at which a function is declared and its relationship to the surrounding variables. Coming from JS, I had a little trouble getting my head around them.

Ground 0: Function Declarations

Defining inline, full-blooded functions in Swift is pretty standard, and could probably be intuited by any engineer with one or two languages under their hat:

func multiply(number: Int, by:Int) -> Int {
	return number * b
}

It would be perfectly valid syntax to pass this function around by simply referencing its identifier (multiply).

Removing some syntax

But what if we don't want to have to declare named functions everywhere. Especially if we're literally just going to use it once?

In JavaScript you define the function inline with the same syntax as you would anywhere else. BUT NOT IN SWIFT. Why? I don't know, friend, by let's explore the what not the why first.

Swift comes with a set of syntactical sugar for declaring closures. Syntactical sugar is a way of making code shorter or more readable. Syntactical sugar often replaces boilerplate or verbose code, and results in identical functionality to its un-sugared sibling.

This syntactical sugar looks like:

	{ (parameters) -> ReturnType in
		// expression
	}

Let's give a real simple example. We're going to take an array of Int and convert it to an Array<String>:

let strings: Array<String> = [1,2,3,4,5].map({ (n: Int) -> String in
	return "The number is \(n)"
})

The in keyword

This syntax looked odd to me. The key (pun intended) to me grokking it was understanding the in keyword. Typically I have only seen this associated with iterators (in languages like python and JS):

# This is Python code
for name in list_of_name:
	print(f"Hello, {name}")

In Closures, in Swift, the in keyword signifies that we've reached the end of our parameter and return type. Everything after the in is the action function expression.

Implicit Types for Even Less Syntax

You better believe that's not all the syntactic sugar. There's still stuff we're going to get rid of.

Once we declare types in one place, e.g. in a variable, generic, parameter, then we don't need to duplicate that typing. We can but we don't have to.

Swift is able to find the implied types elsewhere in the code, and therefore we can remove them in the closure.

In the following example the boastfulString variable is declared as the Array<String> type, so the map function doesn't need to be told twice:

let boastfulStrings: Array<String> = [10,20,30,40,50].map( { n in return "I have \(n) tacos!"})

By using implicit types we can get rid of two redundant parts of the closure declaration.

  • The surrounding parentheses for the parameters: (n: Int) becomes n). We can do the same with multiple parameters: Say we had an argument that took three parameters: (name: String, age: Int, averageScore: Float) .. could become name, age, averageScore ..
  • The return type of the closure: We know the function needs to return us a String, it's in the variable declaration. So (n: Int) -> Int in .. can become n -> ..

Swift is super ready for you to give it an Array<String> and will actually get pretty mad if you dont'. You'll run into compile-time errors with anything else.

Implicit Returns

Man, talking of redundancies, that return doesn't look like it's doing much on that one line there. JS, Ruby, and Rust all have implicit return types - and so does Swift. That means you don't need to use the return keyword to tell Swift "this is the bed that the function should hand back".

This is more syntactic sugar: we're choosing conciseness and simplicity over explicit and verbose. Having return or omitting it in this example does exactly the same thing. You don't have to like this, or us it in your code. It's your choice, but you should definitely know about it. Also it's probably useful in those scenarios where you just need something to work:

let boastfulStrings: Array<String> = [100, 150, 200].map( { n in "I have \(n) taco!" })

Look how concise that statement is.

Shorthand Argument Names: spend those $

But we can be more concise.

What else can we get rid of?

That named parameter, n:

let regretfulStrings: Array<String> = [2,4,6,8].map( { "I ate \($0) too many tacos ):" })

These are shorthand arguments. Where $0 refers to the first argument in the function. $1 to the second argument, $2 to the third...

Now we're really favouring conciseness over explicitness.

Legally questionable copyright notice© 2021 Thomas Wilson