Swift: Add 2 numbers using function and closure
Video: Swift: Add 2 numbers using function and closure by Taught by Celeste AI - AI Coding Coach
Swift with Copilot: Add 2 Numbers — Function and Closure
Function form:
func add(_ a: Int, _ b: Int) -> Int { a + b }. Closure form:let add: (Int, Int) -> Int = { $0 + $1 }. Same call siteadd(3, 5). Different declaration syntax; identical type.
A side-by-side that reinforces episode 13.
The Copilot prompt
// Add two numbers using both a function and a closure
Copilot completes:
// Function
func addFn(_ a: Int, _ b: Int) -> Int {
a + b
}
// Closure
let addCl: (Int, Int) -> Int = { $0 + $1 }
print(addFn(3, 5)) // 8
print(addCl(3, 5)) // 8
Both produce 8. The call site is identical.
Function declaration syntax
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
func— keyword.add— name._— suppress argument label (soadd(3, 5)instead ofadd(a: 3, b: 5)).a: Int, b: Int— parameters.-> Int— return type.{ ... }— body.
For a single-expression body, you can omit return:
func add(_ a: Int, _ b: Int) -> Int {
a + b
}
Closure literal syntax
let add: (Int, Int) -> Int = { (a, b) in
return a + b
}
(Int, Int) -> Int— explicit type annotation.{ (a, b) in ... }— closure literal.(a, b)— parameters; types inferred from the annotation.in— separator between parameters and body.return ...— optional for single-expression closures.
Compact form using $N:
let add: (Int, Int) -> Int = { $0 + $1 }
Shortest form. $0 and $1 are the first two arguments.
Inferring types
If you assign without an annotation:
let add = { (a: Int, b: Int) in a + b }
The closure parameters need types — the compiler can't infer from a literal. Add to the parameter list, and the return type is inferred.
Calling them
addFn(3, 5) // 8
addCl(3, 5) // 8
Same syntax. The closure variable is callable just like a function.
You can also pass either to a higher-order function:
func applyTwice(_ op: (Int, Int) -> Int, to a: Int, and b: Int) -> Int {
op(op(a, b), op(a, b)) // ((a+b)+(a+b))
}
applyTwice(addFn, to: 3, and: 5) // 16
applyTwice(addCl, to: 3, and: 5) // 16
applyTwice(+, to: 3, and: 5) // 16 — operators are functions too
Storing in a collection
let operations: [(Int, Int) -> Int] = [
addFn,
addCl,
{ $0 - $1 },
*,
]
for op in operations {
print(op(10, 3))
}
// 13 13 7 30
Functions, closures, and operators all interoperate as values.
Default arguments (function only)
func add(_ a: Int, _ b: Int = 1) -> Int {
a + b
}
add(5) // 6
add(5, 3) // 8
Closures don't support default arguments. If you need defaults, use a function.
Argument labels (function only)
func subtract(from total: Int, value: Int) -> Int {
total - value
}
subtract(from: 10, value: 3) // 7
Closures have no argument labels — always positional.
For self-documenting code, functions are better. For pipelines, closures' brevity wins.
When to choose which
Function: - Named, reusable. - Documentation matters. - Default arguments needed. - Argument labels improve readability. - Recursive (closures can reference themselves only via tricks).
Closure:
- One-line transform inside map/filter/reduce.
- Inline callback (onTap: { ... }).
- Capturing surrounding state.
- Short and used once.
For "add two numbers as a public API," a function:
public func add(_ a: Int, _ b: Int) -> Int { a + b }
For "I need a callback that adds 5":
let addFive: (Int) -> Int = { $0 + 5 }
Generic versions
To accept any numeric type:
func add<T: Numeric>(_ a: T, _ b: T) -> T {
a + b
}
add(3, 5) // 8 (Int)
add(3.5, 1.5) // 5.0 (Double)
Closures don't support generic parameters as cleanly:
// You'd write a function that returns a closure
func makeAdder<T: Numeric>() -> (T, T) -> T {
return { $0 + $1 }
}
let addInt: (Int, Int) -> Int = makeAdder()
let addDbl: (Double, Double) -> Double = makeAdder()
For generics, prefer functions.
Common stumbles
Closure type without parens. (Int, Int) -> Int — the parens around the input types are required, even for one parameter ((Int) -> Int).
return in single-expression closure. Optional. { $0 + $1 } works without it.
Closure can't be recursive. A closure can't reference itself by name (until it's fully defined). For recursion, use a function.
Default args in closures. Not supported. Use a function or a wrapper.
Argument labels in closures. Not supported. Always positional.
Function reference syntax. let f = add(_:_:) (with the _:_: for parameters). Useful when there are overloads to disambiguate.
What's next
Episode 15: Add 1 to each element in an array. map for transforms.
Recap
Function: func add(_ a: Int, _ b: Int) -> Int { a + b }. Closure: let add: (Int, Int) -> Int = { $0 + $1 }. Both have type (Int, Int) -> Int; both callable identically. Functions support default args, labels, generics, recursion. Closures support compact syntax, capture, inline use. Pick based on context — named/reusable → function; inline/one-shot → closure.
Next episode: map +1.