본문 바로가기
YAGOM CAREER STARTER

[TIL] 20230124: Generics

by Rhode 2023. 1. 26.

본문은 야곰 아카데미 커리어 스타터 캠프를 통해 학습한 내용을 회고한 글입니다.


Generics

swift 표준 라이브러리의 대부분은 제네릭 코드로 빌드된다. 대부분이 인지하지 못하고 있지만, Language Guide 전체에서 제네릭을 사용하고 있다. 예를 들어 swift의 어레이와 딕셔너리 유형은 모두 제네릭 컬렉션이다. Int값을 포함하는 배열, String값을 포함하는 배열 혹은 실제로 swift에서 생성할 수 있는 다른 유형의 배열을 만들 수 있다. 마찬가지로 지정된 유형의 값을 저장하는 딕셔너리를 만들 수 있으며 해당 유형에 제한이 없다.

다음과 같은 func가 있다고 해보자.
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

 swapTwoInts(_:_:) 메서드는 Int 값에 대해서만 사용될 수 있다. 그래서 만약에 두개의 String값이나 Double값을 바꿔치기 하고 싶다면, 다음처럼 swapTwoStrings(_:_:)와 swapTwoDoubles(_:_:)메서드를 구현해줘야한다.

func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let temporaryA = a
    a = b
    b = temporaryA
}

func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
    let temporaryA = a
    a = b
    b = temporaryA
}

여기에서 swapTwoInts(_:_:), swapTwoStrings(_:_:) 그리고 swapTwoStrings(_:_:)의 바디가 동일하다는 것을 알 수 있다. 유일한 차이는 이 메서드들이 택하는 값이 Int, String, 그리고 Double이라는 것이다. 이럴 때는 any 타입의 두개의 값을 바꾸는 하나의 메서드로 작성하는 것이 더 유용하고 유연하다. 아래와 같이 제네릭 버전의 메서드를 구현할 수 있다:

 

Generic Functions

제네릭 메서드는 어떠한 타입으로도 작동시킬 수 있다. swapTwoValues(_:_:)는 swapTwoInts(_:_:)의 제네릭한 버전이라고 볼 수 있다:

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

swapTwoValues(_:_:) 메서드의 바디는 swapTwoInts(_:_:)의 바디와 같지만 첫줄에서 차이를 느낄 수 있다:

func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues<T>(_ a: inout T, _ b: inout T)

제네릭 버전의 메서드는 (Int, String 혹은 Double과 같은) 실질적인 타입 이름 대신에 (이 경우에는 T라고 불리는) 플레이스홀더 타입 이름을 사용한다. 플레이스 홀더 타입 이름은 T가 어떤 타입이 되어야하는지에 대해서 얘기하지 않는다. 대신에 T가 무엇이든간에 a와 b가 같은 타입 T여야한다는 것을 얘기한다. 

또 다른 차이는 제네릭 메서드의 이름 swapTwoValues(_:_:) 뒤에 꺽쇠 표시 안에 플레이스홀더 타입 이름 T(<T>)가 있는 것이 있다는 것이다. 이 꺽쇠표시는 swift에게 swapTwoValues(_:_:)내에서 T가 플레이스홀더 타입 이름이라는 것을 말해준다. T가 플레이스홀더이기 때문에, swift는 T라는 실질적인 타입을 찾으려고 하지 않는다.

 

Type Parameters

위의 swapTwoValues(_:_:)에서, 플레이스홀더 타입 T는 타입 매개변수의 예시이다. 타입 매개변수는 플레이스홀더 타입을 명시하고 이름지어준다. 그리고 메서드의 이름 뒤에 꺽쇠를 단 채 적힌다. (<T>처럼)

일단 한 번 타입 매개변수를 명시하면, 메서드의 매개변수의 타입을 정의하기 위해 사용할 수 있다. (swapTwoValues(_:_:)의 a와 b 매개변수처럼) 혹은 메서드의 반환값이나 타입 명시로서 사용할 수 있다. 각각의 경우들에 대해서, 메서드가 호출될 때마다 타입 매개변수는 실질적인 타입을 대체한다. 

꺽쇠 안에 쉼표로 구분해서 여러개의 타입 매개변수 이름을 씀으로서 한개 이상의 타입 매개변수를 제공할 수 있다.

 

Naming Type Parameters

대부분의 케이스들에서, 타입 매개변수는 Dictionary<Key, Value>에서 Key와 Value혹은 Array<Element>에서 Element와 같이 설명적인 이름을 가져왔다. 그리고 이것은 코드를 읽는 사람으로 하여금 타입 매개변수와 제네릭 타입 혹은 메서드의 관계에 대해 알 수 있게 한다. 하지만, 그 사이에 유의미한 관계가 없을 땐, T, U, 혹은 V 등으로 이름을 짓는게 일반적이다.

 

<To be continued...>