🔥 제네릭 Where 절을 사용한 확장

570자
7분

제네릭 where 절은 확장의 일부로도 사용할 수 있어요. 아래 예제는 이전 예제에서 다룬 제네릭 Stack 구조체를 확장하여 isTop(_:) 메서드를 추가하는 걸 보여주고 있습니다.

extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            // 스택이 비어있으면 false를 반환해요.
            return false
        }
        // 주어진 item과 스택의 최상위 item을 비교하는 거예요.
        return topItem == item
    }
}
swift

이 새로운 isTop(_:) 메서드는 먼저 스택이 비어있는지 확인하고, 그렇지 않으면 주어진 item과 스택의 최상위 item을 비교합니다. 만약 제네릭 where 절 없이 이를 시도한다면, 문제가 생길 거예요. isTop(_:) 메서드의 구현은 == 연산자를 사용하지만, Stack의 정의는 그 항목들이 동등 비교가 가능할 것을 요구하지 않거든요. 따라서 == 연산자를 사용하면 컴파일 시간 오류가 발생하게 됩니다. 제네릭 where 절을 사용하면 확장에 새로운 요구사항을 추가할 수 있어서, 스택의 항목들이 동등 비교 가능할 때에만 isTop(_:) 메서드를 추가할 수 있게 돼요.

아래는 isTop(_:) 메서드가 실제로 어떻게 동작하는지 보여주는 예시입니다.

if stackOfStrings.isTop("tres") {
    print("Top element is tres.")
} else {
    print("Top element is something else.")
}
// "Top element is tres."를 출력해요.
swift

만약 동등 비교가 불가능한 요소들로 이루어진 스택에서 isTop(_:) 메서드를 호출하려고 하면, 컴파일 시간 오류가 발생할 거예요.

struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue)  // 오류가 나겠죠?
swift

제네릭 where 절은 프로토콜 확장에서도 사용할 수 있답니다. 아래 예제는 이전 예제에서 다룬 Container 프로토콜을 확장하여 startsWith(_:) 메서드를 추가하는 걸 보여주고 있어요.

extension Container where Item: Equatable {
    func startsWith(_ item: Item) -> Bool {
        // 컨테이너에 적어도 하나의 항목이 있는지 확인하는 거예요.
        return count >= 1 && self[0] == item
    }
}
swift

startsWith(_:) 메서드는 먼저 컨테이너에 적어도 하나의 항목이 있는지 확인하고, 그 후에 컨테이너의 첫 번째 항목이 주어진 항목과 일치하는지 확인합니다. 이 새로운 startsWith(_:) 메서드는 위에서 사용된 스택과 배열을 포함하여, Container 프로토콜을 따르는 모든 타입에서 사용될 수 있어요. 단, 컨테이너의 항목들이 동등 비교 가능해야 한다는 점, 잊지 마세요!

if [9, 9, 9].startsWith(42) {
    print("Starts with 42.")
} else {
    print("Starts with something else.")
}
// "Starts with something else."를 출력하겠죠?
swift

위의 예제에서 제네릭 where 절은 Item이 프로토콜을 따를 것을 요구하지만, Item이 특정 타입일 것을 요구하는 제네릭 where 절을 작성할 수도 있어요. 예를 들면 이런 식으로요.

extension Container where Item == Double {
    func average() -> Double {
        var sum = 0.0
        for index in 0..<count {
            sum += self[index]
        }
        // count를 Int에서 Double로 명시적으로 변환하여 부동 소수점 나눗셈을 할 수 있도록 하는 거예요.
        return sum / Double(count)
    }
}
print([1260.0, 1200.0, 98.6, 37.0].average())
// "648.9"를 출력하게 될 거예요.
swift

이 예제는 Item 타입이 Double인 컨테이너에 average() 메서드를 추가하고 있습니다. 이 메서드는 컨테이너의 항목들을 순회하면서 합계를 구하고, 컨테이너의 개수로 나누어 평균을 계산하는 거죠.

확장의 일부로 작성하는 제네릭 where 절에서는 다른 곳에서 제네릭 where 절을 작성할 때처럼 여러 개의 요구사항을 포함시킬 수 있어요. 목록에서 각 요구사항은 쉼표로 구분하면 된답니다.

제네릭 where 절을 활용하면 제네릭 코드의 유연성과 표현력을 크게 높일 수 있습니다. 이를 통해 특정 타입이나 프로토콜을 따르는 타입들에 대해서만 추가적인 기능을 제공하는 확장을 작성할 수 있게 되는 거죠. 이런 방식으로 제네릭 코드의 재사용성을 향상시키면서도, 타입 안전성을 유지할 수 있게 됩니다.

다음은 제네릭 where 절의 활용 예시를 시각화한 다이어그램이에요.

lecture image

이 다이어그램은 제네릭 타입이 제네릭 where 절을 가진 확장을 통해, 특정 타입이나 프로토콜을 따르는 타입들에 대해 추가 기능을 제공할 수 있다는 걸 보여주고 있죠. 이런 방식으로 제네릭 프로그래밍의 강력함과 유연성을 활용할 수 있답니다.

어떤가요? 제네릭 where 절의 매력을 느낄 수 있었나요? 이를 활용하면 더 강력하고 유연한 제네릭 코드를 작성할 수 있을 거예요. 한번 직접 사용해보는 건 어떨까요?