함수
함수 (Functions)
함수를 정의하고 호출하는 방법, 인자에 라벨을 붙이는 방법, 그리고 반환 값을 사용하는 방법을 살펴봐요. Swift의 함수 문법은 아주 유연해서, 매개변수 이름조차 없는 단순한 함수부터 각 매개변수에 이름과 라벨이 붙는 복잡한 메서드까지 뭐든 표현할 수 있어요.
함수는 특정 작업을 수행하는, 스스로 완결된 코드 덩어리예요. 함수에 이름을 붙이면 그 이름이 무슨 일을 하는지 드러나고, 필요할 때 그 이름으로 함수를 "호출"해서 작업을 수행시켜요.
Swift의 함수는 저마다 타입이 있어요. 함수 타입은 그 함수의 매개변수 타입과 반환 타입으로 이루어져요. 이 타입을 Swift의 다른 타입처럼 사용할 수 있어서, 함수를 다른 함수의 매개변수로 넘기거나 함수가 함수를 반환하게 만드는 일이 아주 자연스러워요. 또 한 가지, 함수 안에 함수를 써 넣어 유용한 기능을 중첩 함수 스코프로 감출 수도 있어요.
본문
함수 정의와 호출 (Defining and Calling Functions)
함수를 정의할 때, 함수가 입력으로 받는 이름 있고 타입 있는 값들을 하나 이상 정할 수 있어요. 이 값들을 *매개변수(parameter)*라고 불러요. 또 함수가 끝나고 출력으로 돌려줄 값의 타입, 즉 *반환 타입(return type)*도 정할 수 있어요.
모든 함수에는 *함수 이름(function name)*이 있고, 이 이름이 함수가 수행하는 작업을 나타내요. 함수를 사용하려면 그 이름으로 함수를 "호출"하고, 함수 매개변수의 타입과 맞는 입력 값(이 값은 *인자(argument)*라고 불러요)을 넘겨줘요. 함수의 인자는 항상 함수의 매개변수 목록과 같은 순서로 넣어야 해요.
아래 예시의 함수는 greet(person:)라고 불러요. 사람의 이름을 입력으로 받아 그 사람을 위한 인사말을 반환하는, 정확히 그 일을 하기 때문이에요. 이 일을 하려면 person이라는 String 값을 입력 매개변수 하나로 정의하고, 반환 타입은 String으로 정해요. 이 String이 그 사람을 위한 인사말을 담게 돼요.
func greet(person: String) -> String {
let greeting = "Hello, " + person + "!"
return greeting
}
이 모든 정보가 함수의 *정의(definition)*로 뭉쳐지는데, 정의는 func 키워드로 시작해요. 반환 타입은 반환 화살표(return arrow) ->(하이픈 다음 오른쪽 꺾쇠 괄호)로 표시하고, 그다음 반환할 타입의 이름을 써 줘요.
정의는 함수가 무엇을 하는지, 무엇을 받기를 기대하는지, 끝나면 무엇을 반환하는지 설명해요. 덕분에 코드의 다른 곳에서 함수를 헷갈림 없이 호출하기 쉬워요.
print(greet(person: "Anna"))
// Prints "Hello, Anna!"
print(greet(person: "Brian"))
// Prints "Hello, Brian!"
greet(person:) 함수를 호출할 때는 person 인자 라벨 뒤에 String 값을 넘겨요. 예를 들어 greet(person: "Anna")처럼요. 함수가 String 값을 반환하니까, print(_:separator:terminator:) 함수 호출 안에 greet(person:)을 감싸서 그 문자열을 출력하고 반환 값을 직접 볼 수 있어요.
Note:
print(_:separator:terminator:)함수는 첫 번째 인자에 라벨이 없어요. 그리고 나머지 인자들은 기본값이 있어서 생략할 수 있어요. 이런 함수 문법의 변형들은 아래의 "함수 인자 라벨과 매개변수 이름"과 "매개변수 기본값"에서 다룰게요.
greet(person:) 함수의 본문은 greeting이라는 새 String 상수를 정의하고 여기에 간단한 인사말을 담는 것으로 시작해요. 이 인사말은 return 키워드를 써서 함수 밖으로 내보내요. return greeting이라고 적힌 코드 줄에서 함수는 실행을 마치고 greeting의 현재 값을 반환해요.
greet(person:) 함수는 입력 값을 달리해서 여러 번 호출할 수 있어요. 위 예시는 "Anna" 값을 넣었을 때와 "Brian" 값을 넣었을 때 어떤 일이 벌어지는지 보여주는데, 각 경우에 맞춘 인사말이 반환되죠.
함수 본문을 짧게 줄이고 싶으면 메시지 생성과 반환문을 한 줄로 합칠 수도 있어요.
func greetAgain(person: String) -> String {
return "Hello again, " + person + "!"
}
print(greetAgain(person: "Anna"))
// Prints "Hello again, Anna!"
함수 매개변수와 반환 값 (Function Parameters and Return Values)
함수의 매개변수와 반환 값은 Swift에서 아주 자유분방해요. 이름 없는 매개변수 하나만 받는 단순한 유틸리티 함수부터, 표현력 있는 매개변수 이름과 다양한 옵션을 갖춘 복잡한 함수까지 무엇이든 정의할 수 있어요.
매개변수 없는 함수 (Functions Without Parameters)
함수가 입력 매개변수를 반드시 정의해야 하는 건 아니에요. 다음은 입력 매개변수가 없는 함수인데, 호출할 때마다 항상 같은 String 메시지를 반환해요.
func sayHelloWorld() -> String {
return "hello, world"
}
print(sayHelloWorld())
// Prints "hello, world".
아무 매개변수도 받지 않더라도 함수 정의에는 함수 이름 뒤 괄호가 여전히 필요해요. 함수를 호출할 때도 이름 뒤에 빈 괄호 한 쌍을 붙여요.
매개변수가 여러 개인 함수 (Functions With Multiple Parameters)
함수는 입력 매개변수를 여러 개 가질 수 있고, 이들은 함수의 괄호 안에 쉼표로 구분해 적어요.
다음 함수는 사람의 이름과 그 사람에게 이미 인사했는지 여부를 입력으로 받아, 그 사람에게 알맞은 인사말을 반환해요.
func greet(person: String, alreadyGreeted: Bool) -> String {
if alreadyGreeted {
return greetAgain(person: person)
} else {
return greet(person: person)
}
}
print(greet(person: "Tim", alreadyGreeted: true))
// Prints "Hello again, Tim!"
greet(person:alreadyGreeted:) 함수를 호출할 때는 괄호 안에서 person이라는 라벨의 String 인자 값과 alreadyGreeted라는 라벨의 Bool 인자 값을 쉼표로 구분해 함께 넘겨요. 이 함수가 앞 절에서 본 greet(person:) 함수와 다르다는 점을 눈여겨보세요. 두 함수 모두 greet로 시작하는 이름을 갖고 있지만, greet(person:alreadyGreeted:)는 인자를 두 개 받는 반면 greet(person:)은 하나만 받아요.
반환 값 없는 함수 (Functions Without Return Values)
함수가 반환 타입을 반드시 정의해야 하는 것도 아니에요. 다음은 greet(person:) 함수의 변형인데, 값을 반환하는 대신 자기 자신이 String 값을 출력해요.
func greet(person: String) {
print("Hello, \(person)!")
}
greet(person: "Dave")
// Prints "Hello, Dave!"
값을 반환할 필요가 없으니까, 함수 정의에는 반환 화살표(->)나 반환 타입이 없어요.
Note: 엄밀히 말하면, 이 버전의
greet(person:)함수는 반환 값을 정의하지 않았는데도 여전히 값을 반환해요. 반환 타입을 정의하지 않은 함수는Void타입의 특별한 값을 반환하는데, 이건 그냥()라고 적는 빈 튜플이에요.
함수의 반환 값은 호출할 때 무시할 수 있어요.
func printAndCount(string: String) -> Int {
print(string)
return string.count
}
func printWithoutCounting(string: String) {
let _ = printAndCount(string: string)
}
printAndCount(string: "hello, world")
// prints "hello, world" and returns a value of 12
printWithoutCounting(string: "hello, world")
// prints "hello, world" but doesn't return a value
첫 번째 함수 printAndCount(string:)은 문자열을 출력한 다음 그 문자의 개수를 Int로 반환해요. 두 번째 함수 printWithoutCounting(string:)은 첫 번째 함수를 호출하지만 반환 값은 무시해요. 두 번째 함수를 호출하면 첫 번째 함수가 여전히 메시지를 출력하지만, 반환된 값은 사용되지 않아요.
Note: 반환 값은 무시할 수 있지만, 값을 반환한다고 말한 함수는 반드시 항상 그 값을 반환해야 해요. 반환 타입이 정의된 함수는 값 없이 함수 끝으로 제어가 흘러나가게 할 수 없고, 그렇게 시도하면 컴파일 타임 에러가 나요.
반환 값이 여러 개인 함수 (Functions with Multiple Return Values)
튜플 타입을 함수의 반환 타입으로 사용하면 여러 값을 하나의 복합 반환 값의 일부로 반환할 수 있어요.
아래 예시는 minMax(array:)라는 함수를 정의하는데, Int 값들의 배열에서 가장 작은 수와 가장 큰 수를 찾아요.
func minMax(array: [Int]) -> (min: Int, max: Int) {
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
minMax(array:) 함수는 Int 값 두 개를 담은 튜플을 반환해요. 이 값들은 min과 max라는 라벨이 붙어 있어서, 함수의 반환 값을 조회할 때 이름으로 접근할 수 있어요.
minMax(array:) 함수의 본문은 currentMin과 currentMax라는 두 작업 변수를 배열의 첫 번째 정수 값으로 설정하는 것에서 시작해요. 그런 다음 배열의 나머지 값들을 순회하면서 각 값을 currentMin과 currentMax의 값보다 작은지 큰지 확인해요. 마지막으로 전체 최솟값과 최댓값을 Int 값 두 개의 튜플로 반환해요.
튜플의 멤버 값들이 함수의 반환 타입의 일부로 이름 붙여져 있으니까, 점 문법으로 접근해 찾은 최솟값과 최댓값을 얻을 수 있어요.
let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
print("min is \(bounds.min) and max is \(bounds.max)")
// Prints "min is -6 and max is 109".
튜플의 멤버는 함수에서 튜플을 반환하는 시점에 이름을 붙일 필요가 없어요. 왜냐하면 그 이름들이 이미 함수의 반환 타입의 일부로 지정돼 있기 때문이에요.
옵셔널 튜플 반환 타입 (Optional Tuple Return Types)
함수에서 반환될 튜플 타입이 "값이 없는" 상태일 가능성이 있으면, 옵셔널 튜플 반환 타입을 써서 전체 튜플이 nil이 될 수 있음을 나타내요. 옵셔널 튜플 반환 타입은 튜플 타입의 닫는 괄호 뒤에 물음표를 붙여서 적어요. 예를 들어 (Int, Int)?나 (String, Int, Bool)?처럼요.
Note:
(Int, Int)?같은 옵셔널 튜플 타입은(Int?, Int?)처럼 옵셔널 타입을 담는 튜플과는 달라요. 옵셔널 튜플 타입에서는 튜플 전체가 옵셔널이지, 튜플 안의 각 값 하나하나가 옵셔널인 게 아니에요.
위의 minMax(array:) 함수는 Int 값 두 개를 담은 튜플을 반환해요. 하지만 이 함수는 넘겨받은 배열에 어떤 안전 검사도 하지 않아요. array 인자로 빈 배열이 들어오면, 위에서 정의한 minMax(array:) 함수는 array[0]에 접근하려다 런타임 에러를 일으켜요.
빈 배열을 안전하게 처리하려면 minMax(array:) 함수를 옵셔널 튜플 반환 타입으로 작성하고, 배열이 비었을 때 nil을 반환하게 해요.
func minMax(array: [Int]) -> (min: Int, max: Int)? {
if array.isEmpty { return nil }
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
옵셔널 바인딩을 쓰면 이 버전의 minMax(array:) 함수가 실제 튜플 값을 반환하는지 nil을 반환하는지 확인할 수 있어요.
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
print("min is \(bounds.min) and max is \(bounds.max)")
}
// Prints "min is -6 and max is 109".
암시적 반환을 쓰는 함수 (Functions With an Implicit Return)
함수의 본문 전체가 단일 표현식이라면, 함수는 그 표현식을 암시적으로 반환해요. 예를 들어 아래 두 함수는 같은 동작을 해요.
func greeting(for person: String) -> String {
"Hello, " + person + "!"
}
print(greeting(for: "Dave"))
// Prints "Hello, Dave!"
func anotherGreeting(for person: String) -> String {
return "Hello, " + person + "!"
}
print(anotherGreeting(for: "Dave"))
// Prints "Hello, Dave!"
greeting(for:) 함수의 정의 전체가 반환하는 인사말 그 자체라서, 더 짧은 이 형태를 쓸 수 있어요. anotherGreeting(for:) 함수는 긴 함수처럼 return 키워드를 써서 같은 인사말을 반환해요. 단일 return 줄로만 작성하는 함수라면 어느 것이든 return을 생략할 수 있어요.
속성 getter도 암시적 반환을 쓸 수 있다는 건 "속성" 장에서 보게 될 거예요.
Note: 암시적 반환 값으로 작성하는 코드는 어떤 값을 반환해야 해요. 예를 들어
print(13)을 암시적 반환 값으로 쓸 수는 없어요. 하지만fatalError("Oh no!")처럼 절대 반환하지 않는 함수는 암시적 반환 값으로 쓸 수 있어요. Swift가 그 암시적 반환이 실제로 일어나지 않는다는 걸 알기 때문이에요.
함수 인자 라벨과 매개변수 이름 (Function Argument Labels and Parameter Names)
각 함수 매개변수는 *인자 라벨(argument label)*과 *매개변수 이름(parameter name)*을 둘 다 가져요. 인자 라벨은 함수를 호출할 때 쓰여요. 호출할 때 각 인자는 그 앞에 인자 라벨을 붙여 써요. 매개변수 이름은 함수 구현 안에서 쓰여요. 기본적으로 매개변수는 자기 매개변수 이름을 인자 라벨로 사용해요.
func someFunction(firstParameterName: Int, secondParameterName: Int) {
// In the function body, firstParameterName and secondParameterName
// refer to the argument values for the first and second parameters.
}
someFunction(firstParameterName: 1, secondParameterName: 2)
모든 매개변수는 고유한 이름을 가져야 해요. 여러 매개변수가 같은 인자 라벨을 갖는 것도 가능하지만, 인자 라벨이 고유하면 코드를 더 읽기 쉽게 만들어 줘요.
인자 라벨 지정하기 (Specifying Argument Labels)
인자 라벨은 매개변수 이름 앞에, 공백으로 구분해 적어요.
func someFunction(argumentLabel parameterName: Int) {
// In the function body, parameterName refers to the argument value
// for that parameter.
}
greet(person:) 함수의 변형으로, 사람의 이름과 고향을 받아 인사말을 반환하는 걸 보여줄게요.
func greet(person: String, from hometown: String) -> String {
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// Prints "Hello Bill! Glad you could visit from Cupertino."
인자 라벨을 쓰면 함수를 문장처럼 표현력 있게 호출하면서도, 함수 본문은 의도가 명확하게 읽히도록 만들 수 있어요.
인자 라벨 생략하기 (Omitting Argument Labels)
어떤 매개변수에 인자 라벨을 달고 싶지 않으면, 그 매개변수의 명시적 인자 라벨 대신 밑줄(_)을 적어요.
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
// In the function body, firstParameterName and secondParameterName
// refer to the argument values for the first and second parameters.
}
someFunction(1, secondParameterName: 2)
매개변수에 인자 라벨이 있으면, 함수를 호출할 때 그 인자에 반드시 라벨을 붙여야 해요.
매개변수 기본값 (Default Parameter Values)
함수 안의 어떤 매개변수에든 매개변수의 타입 뒤에 값을 넣어 *기본값(default value)*을 정의할 수 있어요. 기본값이 정의되어 있으면, 함수를 호출할 때 그 매개변수를 생략할 수 있어요.
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
// If you omit the second argument when calling this function, then
// the value of parameterWithDefault is 12 inside the function body.
}
someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault is 6
someFunction(parameterWithoutDefault: 4) // parameterWithDefault is 12
기본값이 없는 매개변수들은 함수의 매개변수 목록의 맨 앞, 기본값이 있는 매개변수들보다 앞에 배치해요. 기본값이 없는 매개변수는 대개 함수의 의미에 더 중요해요. 그것들을 앞에 적으면 기본 매개변수가 생략됐든 아니든 같은 함수를 호출하고 있다는 걸 알아보기 쉬워져요.
가변 매개변수 (Variadic Parameters)
*가변 매개변수(variadic parameter)*는 지정된 타입의 값을 0개 이상 받아들여요. 가변 매개변수를 쓰면 함수를 호출할 때 그 매개변수로 다양한 개수의 입력 값을 넘길 수 있어요. 가변 매개변수는 매개변수의 타입 이름 뒤에 마침표 세 개(...)를 넣어 작성해요.
가변 매개변수로 넘겨진 값들은 함수 본문 안에서 알맞은 타입의 배열로 사용할 수 있어요. 예를 들어 이름이 numbers이고 타입이 Double...인 가변 매개변수는, 함수 본문 안에서 [Double] 타입의 numbers라는 상수 배열로 사용할 수 있어요.
아래 예시는 어떤 길이든 숫자 목록의 *산술 평균(arithmetic mean)*을(줄여서 *평균(average)*이라고도 불러요) 계산해요.
func arithmeticMean(_ numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8.25, 18.75)
// returns 10.0, which is the arithmetic mean of these three numbers
함수는 가변 매개변수를 여러 개 가질 수 있어요. 가변 매개변수 바로 뒤에 오는 첫 매개변수는 인자 라벨이 있어야 해요. 인자 라벨이 어느 인자가 가변 매개변수로 넘어가고 어느 인자가 그 뒤의 매개변수들로 넘어가는지를 모호함 없이 만들어 줘요.
입출력 매개변수 (In-Out Parameters)
함수 매개변수는 기본적으로 상수예요. 함수 본문 안에서 함수 매개변수의 값을 바꾸려 하면 컴파일 타임 에러가 나요. 즉 실수로 매개변수의 값을 바꿀 수가 없어요. 함수가 매개변수의 값을 수정하고, 그 변경이 함수 호출이 끝난 뒤에도 유지되길 원한다면, 그 매개변수를 *입출력 매개변수(in-out parameter)*로 정의해요.
입출력 매개변수는 매개변수의 타입 바로 앞에 inout 키워드를 놓아 작성해요. 입출력 매개변수는 값이 함수 안으로 전달되고, 함수에 의해 수정되며, 원래 값을 대체하도록 함수 밖으로 다시 전달돼요. 입출력 매개변수의 동작과 관련 컴파일러 최적화에 대한 자세한 논의는 "선언" 장에서 볼 수 있어요.
입출력 매개변수의 인자로는 변수만 넘길 수 있어요. 상수나 리터럴 값은 수정될 수 없으니까 인자로 넘길 수 없어요. 변수를 입출력 매개변수의 인자로 넘길 때는 변수 이름 바로 앞에 앰퍼샌드(&)를 놓아, 그 변수가 함수에 의해 수정될 수 있음을 나타내요.
Note: 입출력 매개변수는 기본값을 가질 수 없고, 가변 매개변수는
inout으로 표시할 수 없어요.
swapTwoInts(_:_:)라는 함수의 예시를 볼게요. 이 함수는 a와 b라는 두 입출력 정수 매개변수를 가져요.
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
swapTwoInts(_:_:) 함수는 그냥 b의 값을 a로, a의 값을 b로 바꿔요. 이 함수는 a의 값을 temporaryA라는 임시 상수에 저장하고, b의 값을 a에 할당한 다음, temporaryA를 b에 할당하는 방식으로 값을 바꿔요.
swapTwoInts(_:_:) 함수를 Int 타입의 변수 두 개로 호출해서 그 값들을 바꿀 수 있어요. someInt와 anotherInt의 이름 앞에 앰퍼샌드가 붙어서 swapTwoInts(_:_:) 함수로 전달되는 점을 주목하세요.
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3".
위 예시는 someInt와 anotherInt가 원래 함수 밖에서 정의됐는데도, swapTwoInts(_:_:) 함수에 의해 원래 값이 수정되는 걸 보여줘요.
Note: 입출력 매개변수는 함수에서 값을 반환하는 것과는 달라요. 위의
swapTwoInts예시는 반환 타입도 정의하지 않고 값을 반환하지도 않지만, 여전히someInt와anotherInt의 값을 수정해요. 입출력 매개변수는 함수가 자기 함수 본문의 스코프 밖에서 효과를 내는 또 다른 방법이에요.
함수 타입 (Function Types)
모든 함수는 특정한 *함수 타입(function type)*을 가지며, 이 타입은 함수의 매개변수 타입과 반환 타입으로 이루어져요.
예를 들어:
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
return a * b
}
이 예시는 addTwoInts와 multiplyTwoInts라는 두 개의 단순한 수학 함수를 정의해요. 이 함수들은 각각 Int 값 두 개를 받아 알맞은 수학 연산을 수행한 결과인 Int 값을 반환해요.
이 두 함수의 타입은 모두 (Int, Int) -> Int예요. 이건 이렇게 읽을 수 있어요.
"두 개의 매개변수(둘 다 Int 타입)를 가지면서 Int 타입 값을 반환하는 함수."
매개변수나 반환 값이 없는 함수의 다른 예시를 볼게요.
func printHelloWorld() {
print("hello, world")
}
이 함수의 타입은 () -> Void, 즉 "매개변수가 없고 Void를 반환하는 함수"예요.
함수 타입 사용하기 (Using Function Types)
함수 타입은 Swift의 다른 타입들과 똑같이 사용해요. 예를 들어, 함수 타입의 상수나 변수를 정의하고 그 변수에 알맞은 함수를 할당할 수 있어요.
var mathFunction: (Int, Int) -> Int = addTwoInts
이건 이렇게 읽을 수 있어요.
"'두 Int 값을 받아 Int 값을 반환하는 함수' 타입인 mathFunction이라는 변수를 정의하고, 이 새 변수가 addTwoInts라는 함수를 가리키도록 설정해요."
addTwoInts(_:_:) 함수는 mathFunction 변수와 같은 타입이라서, 이 할당은 Swift의 타입 검사기에 의해 허용돼요.
이제 할당된 함수를 mathFunction이라는 이름으로 호출할 수 있어요.
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 5".
타입이 같은 다른 함수도, 함수가 아닌 타입과 마찬가지 방식으로 같은 변수에 할당할 수 있어요.
mathFunction = multiplyTwoInts
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 6".
다른 타입들과 마찬가지로, 함수를 상수나 변수에 할당할 때 함수 타입을 Swift가 추론하도록 맡길 수도 있어요.
let anotherMathFunction = addTwoInts
// anotherMathFunction is inferred to be of type (Int, Int) -> Int
함수 타입을 매개변수 타입으로 (Function Types as Parameter Types)
(Int, Int) -> Int 같은 함수 타입을 다른 함수의 매개변수 타입으로 사용할 수 있어요. 이렇게 하면 함수 구현의 어떤 측면은 호출 시점에 함수 호출자가 제공하도록 남겨둘 수 있어요.
위의 수학 함수들의 결과를 출력하는 예시를 볼게요.
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// Prints "Result: 8".
이 예시는 매개변수 세 개를 가진 printMathResult(_:_:_:)라는 함수를 정의해요. 첫 번째 매개변수는 mathFunction이고 타입이 (Int, Int) -> Int예요. 이 첫 매개변수의 인자로는 그 타입의 함수라면 무엇이든 넘길 수 있어요. 두 번째와 세 번째 매개변수는 a와 b이고 둘 다 Int 타입이에요. 이 값들은 제공된 수학 함수의 두 입력 값으로 쓰여요.
printMathResult(_:_:_:)를 호출하면 addTwoInts(_:_:) 함수와 정수 값 3과 5가 넘겨져요. 제공된 함수를 값 3과 5로 호출하고, 결과인 8을 출력해요.
printMathResult(_:_:_:)의 역할은 알맞은 타입의 수학 함수를 호출한 결과를 출력하는 거예요. 그 함수의 구현이 실제로 무엇을 하는지는 중요하지 않아요. 그저 함수가 올바른 타입인지만 중요해요. 이렇게 하면 printMathResult(_:_:_:)가 자기 기능 중 일부를 타입 안전한 방식으로 함수 호출자에게 넘겨줄 수 있어요.
함수 타입을 반환 타입으로 (Function Types as Return Types)
함수 타입을 다른 함수의 반환 타입으로 사용할 수 있어요. 반환하는 함수의 반환 화살표(->) 바로 뒤에 완전한 함수 타입을 작성하면 돼요.
다음 예시는 stepForward(_:)와 stepBackward(_:)라는 두 개의 단순한 함수를 정의해요. stepForward(_:) 함수는 입력 값보다 하나 큰 값을 반환하고, stepBackward(_:) 함수는 입력 값보다 하나 작은 값을 반환해요. 두 함수 모두 타입이 (Int) -> Int예요.
func stepForward(_ input: Int) -> Int {
return input + 1
}
func stepBackward(_ input: Int) -> Int {
return input - 1
}
여기 반환 타입이 (Int) -> Int인 chooseStepFunction(backward:)라는 함수가 있어요. chooseStepFunction(backward:) 함수는 backward라는 Boolean 매개변수에 따라 stepForward(_:) 함수 또는 stepBackward(_:) 함수를 반환해요.
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
return backward ? stepBackward : stepForward
}
이제 chooseStepFunction(backward:)를 사용해 어느 한 방향으로 움직일 함수를 얻을 수 있어요.
var currentValue = 3
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero now refers to the stepBackward() function
위 예시는 currentValue라는 변수를 점점 0에 가깝게 옮기려면 양수 단계가 필요한지 음수 단계가 필요한지 판단해요. currentValue는 초기 값이 3이라서, currentValue > 0이 true를 반환하고, 그 결과 chooseStepFunction(backward:)가 stepBackward(_:) 함수를 반환해요. 반환된 함수에 대한 참조는 moveNearerToZero라는 상수에 저장돼요.
이제 moveNearerToZero가 올바른 함수를 가리키므로, 0까지 세는 데 사용할 수 있어요.
print("Counting to zero:")
// Counting to zero:
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// 3...
// 2...
// 1...
// zero!
중첩 함수 (Nested Functions)
이 장에서 지금까지 본 모든 함수는 *전역 함수(global function)*의 예시였는데, 전역 함수는 전역 스코프에서 정의돼요. 다른 함수의 본문 안에 함수를 정의할 수도 있는데, 이걸 *중첩 함수(nested function)*라고 불러요.
중첩 함수는 기본적으로 바깥 세상에 숨겨져 있지만, 자기 바깥 함수(enclosing function)에 의해 호출되고 사용될 수는 있어요. 바깥 함수는 자기 중첩 함수 중 하나를 반환해서 다른 스코프에서 사용하게 할 수도 있어요.
위의 chooseStepFunction(backward:) 예시를 중첩 함수를 사용하고 반환하도록 다시 쓸 수 있어요.
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backward ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero now refers to the nested stepForward() function
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!
더 알아보기
- 함수 정의의 기본 뼈대:
func키워드, 매개변수 목록, 반환 화살표->와 반환 타입. - 매개변수는 인자 라벨(호출 시)과 매개변수 이름(구현 안)을 따로 가질 수 있고, 밑줄
_로 라벨을 생략하거나inout으로 입출력 매개변수를 만들 수 있어요. - 함수도 타입이 있어서, 다른 함수의 매개변수나 반환 타입으로 쓸 수 있어요.
- 관련 주제로는 "속성"(암시적 반환 getter), "선언"(입출력 매개변수 동작), "클로저" 장이 이어져요.