OpaqueTypes
opaque Type을 반환하는 함수는 반환 값의 타입을 감춘다.
함수의 반환 타입으로 구체적인 타입이 아니라, 반환 값이 채택한 protocol 관점에서 설명된다고 한다.
모듈과 모듈을 호출하는 코드 사이의 경계에서 반환값의 실제 타입을 private으로 남을 수 있기 때문에 반환 값의 타입을 감추는 것은 유용하다.
프로토콜 타입인 값을 반환하는 것과 달리 opaque 타입은 타입 정체성을 보존한다.
즉, 컴파일러는 타입 정보에 접근할 수 있지만 모듈의 클라이언트는 그럴 수 없다.
Opaque Type이 해결하는 문제
protocol Shape {
func draw() -> String
}
struct Triangle: Shape {
var size: Int
func draw() -> String {
var result: [String] = []
for length in 1...size {
result.append(String(repeating: "*", count: length))
}
return result.joined(separator: "\n")
}
}
let smallTriangle = Triangle(size: 3)
print(smallTriangle.draw())
// *
// **
// ***
ASCII 도형을 그리는 모듈이 있다고 하자.
ASCII 도형의 기본 특성은 Shape 프로토콜에 대한 요구사항인 도형의 문자열을 반환하는 draw() 함수이다.
따라서 코드에서 사이즈가 3개인 triangle을 그려주는 걸 확인할 수 있다.
이때 모양을 뒤집는 작업을 아래와 같이 Generic을 사용해서 구현할 수 있다.
struct FlippedShape<T: Shape>: Shape {
var shape: T
func draw() -> String {
let lines = shape.draw().split(separator: "\n")
return lines.reversed().joined(separator: "\n")
}
}
let flippedTriangle = FlippedShape(shape: smallTriangle)
print(flippedTriangle.draw())
// ***
// **
// *
그러나 이러한 접근에는 제한이 존재한다.
바로 FilippedShape를 구현하는데 어떠한 Generic 타입을 사용했는지가 명확하게 노출된다는 제한이다.
이렇게 Generic을 사용해서 접근하게 되면 위에서 생성한 2개를 수직으로 결합하기 위해서는
뒤집힌 삼각형과 일반 삼각형을 결합하는 JoinedShape<FlippedShape<Triangle>, Triangle> 같은 구조체를 생성하게 된다.
struct JoinedShape<T: Shape, U: Shape>: Shape {
var top: T
var bottom: U
func draw() -> String {
return top.draw() + "\n" + bottom.draw()
}
}
let joinedTriangles = JoinedShape(top: smallTriangle, bottom: flippedTriangle)
print(joinedTriangles.draw())
// *
// **
// ***
// ***
// **
// *
생성하는 도형에 대한 자세한 타입 정보를 노출하게되면 전체 반환 유형을 반드시 명시해야만 하기 때문에
ASCII 도형 모듈의 public 인터페이스에 포함되지 않는 타입들이 노출될 수 있다.
이때 실제로 모듈 내부의 코드는 다양한 방식으로 똑같은 도형을 만들 수 있으며,
모듈 외부에서 도형을 사용하는 코드는 변환 목록에 대해서 구현 세부 정보를 고려할 필요가 없다.
따라서 JoinedShape과 FlippedShape와 같은 Wrapper 타입들은 모듈의 사용자에게는 중요하지 않으므로 보여질 필요가 없다.
모듈의 public 인터페이스는 도형을 결합하고, 뒤집는 것과 같은 함수들로 구성되면 되고 이러한 함수는 다른 Shape 값을 반환하면 된다.
즉, 외부 사용자는 필요한 것만 알면되지 불필요한 걸 노출하지 말고 감출 수 있다면 감추는게 좋다.
Opaque Type의 반환
Opaque type은 제네릭 타입의 반대라고 생각할 수 있다.
제네릭 타입은 함수를 호출하는 코드에서 매개변수의 타입을 선택하고, 함수 구현에서 추상화된 방식으로 값을 반환할 수 있다.
예시는 아래와 같다.
func max<T>(_ x: T, _ y: T) -> T where T: Comparable { ... }
max 함수를 호출하는 코드는 x, y의 타입에 따라서 T가 결정되게 된다.
이때 Comparable 프로토콜을 채택하는 모든 타입은 x, y의 타입이 될 수 있다.
함수의 내부 코드는 호출자가 제공하는 타입이 무엇이든 처리할 수 있도록 작성되게 된다.
따라서 max 함수의 구현은 모든 Comparable 타입이 공유하는 기능만 사용할 수 있게 된다.
그러나 만약 opaque type을 사용하게 되면 상황이 달라진다.
opaque type은 함수 구현에서 함수를 호출하는 코드에서 추상화 된 방식으로 반환되는 값의 타입을 선택할 수 있다.
아래 예시 함수는 기본 타입을 노출하는 것이 아니라 사다리꼴을 반환하게 된다.
struct Square: Shape {
var size: Int
func draw() -> String {
let line = String(repeating: "*", count: size)
let result = Array<String>(repeating: line, count: size)
return result.joined(separator: "\n")
}
}
func makeTrapezoid() -> some Shape { // ⭐️
let top = Triangle(size: 2)
let middle = Square(size: 2)
let bottom = FlippedShaped(shape: top)
let trapezoid = JoinedShape(top: top, bottom: JoinedShape(top: middle, bottom: bottom))
return trapezoid
}
let trapezoid = makeTrapezoid()
print(trapezoid.draw())
// *
// **
// **
// **
// **
// *
⭐️ 이 부분을 보면 makeTrapezoid 함수는 some Shape 를 반환 타입으로 선언하고, return 값으로 구체적인 타입을 지정하지 않고 Shape 프로토콜을 준수하는 특정 타입의 값을 반환하게 된다.
이렇게 makeTrapezoid 함수를 작성하면 public interface의 부분으로 만들어지는 모양을 타입으로 지정하지 않고도 return 값이 Shape로 표현할 수 있게 된다.
이때 함수 내부 구현은 2개의 삼각형과 1개의 사각형을 사용하지만, return 값의 타입을 변경하지 않고도 여러 방법으로 사다리꼴을 구현하도록 수정할 수 있게 된다.
이 예시를 통해서 opaque type은 제네릭 타입과 반대인 것을 확인할 수 있다.
- 함수 호출 부분
- 제네릭 타입: 어떠한 protocol을 채택하는 한 어떠한 타입이든 매개변수로 넣어줄 수 있다
- opaque type: 반환된 모든 Shape 타입의 값과 함께 동작할 수 있도록 일반적으로 작성해야함 (제네릭 함수의 구현 부분처럼)
- 함수 내부 구현 부분
- 제네릭 타입: 호출자가 제공하는 타입이 무엇이든 처리할 수 있도록 일반적으로 작성해야함
- opaque type: Shape 프로토콜을 채택하는 한 어떠한 타입이든 return 값으로 반환할 수 있음 (제네릭 함수 호출 부분처럼)
이런 opaque type은 제네릭과 결합될 수도 있다.
Shape 프로토콜을 준수하는 일부 타입의 값을 반환하는 예시를 보자
func flip<T: Shape>(_ shape: T) -> some Shape {
return FlippedShape(shape: shape)
}
func join<T: Shape, U: Shape>(_ top: T, bottom: U) -> some Shape {
return JoinedShape(top: top, bottom: bottom)
}
let opaquedJoinedTriangle = join(smallTriangle, flip(smallTriangle))
print(opaquedJoinedTriangle.draw())
// *
// **
// ***
// ***
// **
// *
예제의 opaquedJoinedTriangle 변수의 값은 전 섹션인 Opaque Type이 해결하는 문제 섹션에 제네릭 예제의 opaquedJoinedTriangle 와 동일하다.

그러나 예제의 값과는 다르게 flip(), join()은 return 타입을 opaque type으로 반환하는 Shape을 타입으로 가지는 제네릭 함수이기 때문에 타입이 보여지지 않게 된다.
두 함수는 제네릭 타입에 의존하며, 함수의 파라미터가 FlippedShape, JoinedShape 타입에 필요한 타입 정보를 전달하기 때문에 모두 제네릭이다.
opaque type을 반환 값으로 가지는 함수가 여러 위치에서 반환하는 경우에는 모든 반환 값들이 동일한 타입을 가져야만 한다.
제네릭 함수의 경우에도 해당 반환 타입은 함수의 제너릭 타입 파라미터로 사용할 수 있지만 여전히 단일 타입이어야 한다.
아래의 예를 보자.
func invalidFlip<T: Shape>(_ shape: T) -> some Shape {
if shape is Square {
return shape // 🚨 Error: return types don't match
}
return FlippedShape(shape: shape) // 🚨 Error: return types don't match
}
만약 위의 함수에 Square 타입을 매개변수로 호출한다면 Square를 호출할 것이고, 그렇지 않다면 FlippedShape를 호출할 것이다.
이렇게 작성된 함수는 단일 타입의 값을 반환해야한다는 요구 사항을 위반하고, 함수자체가 유효하지 않게 된다.
이 함수를 동작하게 하려면 FlippedShape를 수정하는 것이다.

아래와 같이 수정하면 위의 함수는 항상 FlippedShape 타입을 반환할 수 있게 된다.
struct FlippedShape<T: Shape>: Shape {
var shape: T
func draw() -> String {
if shape is Square {
return shape.draw()
}
let lines = shape.draw().split(separator: "\n")
return lines.reversed().joined(separator: "\n")
}
}
항상 단일 타입을 반환해야한다고 해서, opaque 반환 타입에 제네릭 사용을 제한하는 것은 아니다.
func `repeat`<T: Shape>(shape: T, count: Int) -> some Collection {
return Array<T>(repeating: shape, count: count)
}
이때는 T의 타입에 따라서 반환 값의 타입이 달라지게 된다.
매개변수로 전달되는 shape이 어떤 것이든지 상관없이 repeat 함수는 해당 shape를 생성해서 array 형태로 반환해준다.
그럼에도 불구하고 반환 값은 항상 동일한 기본 타입인 [T] 이므로, opaque 반환 타입을 가진 함수는 단일 타입의 반환 값만 가져야한다는 요구사항을 만족하게 된다.
opaque type과 protocol type의 차이점
함수의 반환 값으로 opaque type과 protocol type을 반환하는 것은 비슷해보이지만, 실제 타입 정체성을 유지하는지 여부가 다르다.
Opaque type은 하나의 특정 타입을 참조하지만 함수 호출자는 어떤 타입인지 볼 수 없는 것이다.
반면 protocol type은 protocol을 준수하는 모든 타입을 참조할 수 있다.
따라서 protocol type은 저장하는 값의 기본 타입에 대해 더 많은 유연성을 제공하고, opaque type은 기본 타입에 대한 더 강력한 보장을 해 줄 수 있다.
예시를 살펴보자.

func protoFlip<T: Shape>(_ shape: T) -> Shape {
return FlippedShape(shape: shape)
}
protoFlip 함수는 flip과 동일한 함수 구현부를 가지며, 항상 같은 타입의 값을 반환한다.
flip과는 다르게 protoFlip이 반환하는 값은 항상 같은 타입을 가질 필요가 없으며 Shape 프로토콜을 준수하기만 하면된다.
즉, protoFlip은 flip이 만드는 것보다 느슨한 API 체결을 만들어준다. 따라서 여러 타입의 값을 반환하는 유연성을 가지게 된다.

func protoFlip<T: Shape>(_ shape: T) -> Shape {
if shape is Square {
return shape
}
return FlippedShape(shape: shape)
}
이 경우에서도 매개변수로 들어오는 T의 종류에 따라서 Square 인스턴스나 FlippedShape 인스턴스를 반환해주게 된다.
그리고 이 함수에 의해서 반환되는 2개의 Shpae 아예 다른 타입을 가진다.
즉, 이 함수의 다른 유효한 버전은 같은 모양의 여러 인스턴스를 뒤집을 때 다른 타입의 값을 반환할 수 있다는 것이다.
그렇기 때문에 protoFlip() 의 구체적이지 않은 반환 타입은 타입의 정보에 많이 의존해야하는 많은 함수들이 반환된 값을 사용할 수 없게된다.
let protoFlippedTrianlge = protoFlip(smallTriangle)
let sameThing = protoFlip(smallTriangle)
protoFlippedTriangle == sameThing // 🚨 Error
따라서 위의 예시와 같이 == 연산자를 사용할 수 없게 된다.
그 이유는 Shape 프로토콜이 == 연산자를 프로토콜의 요구 사항으로 가지고 있지 않기 때문에 발생하게 된다.
그래서 == 연산자를 추가하려고 하면 left-hand, right-hand의 타입을 알아야한다는 조건이 있기 때문에 다시 문제가 발생하게된다.
이러한 종류의 연산자는 일반적으로 프로토콜을 채택하는 구체 타입과 일치하는 Self 타입의 인수를 사용하지만, 프로토콜에 Self 요구사항을 추가하게되면 프로토콜을 타입으로 사용할 때 발생하는 type erasure를 허용하지 않게 된다.
함수의 반환 타입으로 protocol type을 사용하면 protocol을 준수하는 모든 타입을 유연하게 반환할 수 있지만, 이러한 유연성의 대가는 반환된 값에 대해 일부 작업을 수행할 수 없게 되고 위 예제는 == 연산자를 사용할 수 없음을 보여준다.
또한 이 접근의 다른 문제점은 shape의 변형이 중첩되지 않는다는 것이다.
삼각형을 뒤집은 결과는 Shape 타입의 값이고, protoFlip() 은 Shape 프로토콜을 준수하는 어떤 타입의 값을 가진다.
그러나 이때 protocol type의 값은 protoFlip()이 반환하는 Shape를 준수하지 않으므로 프로토콜을 준수하지 않게 된다.
즉, 여러 번형을 적용하는 protoFlip(protoFlip(smallTriangle)) 과 같은 코드에서 뒤집힌 모양은 protoFlip()에 대해 유효하지 않은 인수이기 때문에 유효하지 않게 된다.
반대로 opaque type은 기본 타입의 정체성을 보존하기 때문에 Swift는 연관된 타입을 유추할 수 있으므로 프로토콜 타입을 반환 값으로 사용할 수 없는 위치에 opaque type을 반환 값으로 사용할 수 있다. 예를 보자.
protocol Container {
associatedtype Item
var count: Int { get }
subscript(i: Int) -> Item { get }
}
extension Array: Container { }
이러한 경우에는 연관 타입이 있기 때문에 함수의 반환 타입으로 Container 를 사용할 수 없다.
제네릭 타입이 무엇인지 추론할 수 있는 함수 바디 외부에 충분한 정보가 없으므로 제너릭 반환 타입의 제약 조건으로도 사용할 수 없다.
// 🚨 Error: Protocol with associated types can't be used as a return type
func makeProtocolContainer<T>(item: T) -> Container {
return [item]
}
// 🚨 Error: Not enough information to infer C
func makeProtocolContainer<T, C: Container>(item: T) -> C {
return [item]
}
이때 반환 타입으로 opaque type인 some Container 를 사용하면 함수는 Container를 반환하지만 Container의 타입 지정은 거부하기 때문에 원하는 API 협약을 맺을 수 있게 된다.
func makeOpaqueContainer<T>(item: T) -> some Container {
return [item]
}
let opaqueContainer = makeOpaqueContainer(item: 12)
let twelve = opaqueContainer[0]
print(type(of: twelve))
// Prints "Int"
마지막에 보듯이 twelve의 타입을 Int로 유추되며, 이것은 opaque type에 타입 추론이 동작함을 보여준다.
makeOpaqueContainer() 의 구현부에서 opaque container의 기본 타입은 [T] 이다.
이 경우에는 T 는 Int 이므로 반환 값은 정수의 배열이고, 연관 타입인 Item의 타입은 Int로 유추된다.
그리고 Container의 subscript는 Item을 반환하기 때문에 twelve의 타입도 Int로 유추된다는 것을 알 수 있다.
OpaqueTypes
opaque Type을 반환하는 함수는 반환 값의 타입을 감춘다.
함수의 반환 타입으로 구체적인 타입이 아니라, 반환 값이 채택한 protocol 관점에서 설명된다고 한다.
모듈과 모듈을 호출하는 코드 사이의 경계에서 반환값의 실제 타입을 private으로 남을 수 있기 때문에 반환 값의 타입을 감추는 것은 유용하다.
프로토콜 타입인 값을 반환하는 것과 달리 opaque 타입은 타입 정체성을 보존한다.
즉, 컴파일러는 타입 정보에 접근할 수 있지만 모듈의 클라이언트는 그럴 수 없다.
Opaque Type이 해결하는 문제
ASCII 도형을 그리는 모듈이 있다고 하자.
ASCII 도형의 기본 특성은
Shape프로토콜에 대한 요구사항인 도형의 문자열을 반환하는draw()함수이다.따라서 코드에서 사이즈가 3개인 triangle을 그려주는 걸 확인할 수 있다.
이때 모양을 뒤집는 작업을 아래와 같이 Generic을 사용해서 구현할 수 있다.
그러나 이러한 접근에는 제한이 존재한다.
바로 FilippedShape를 구현하는데 어떠한 Generic 타입을 사용했는지가 명확하게 노출된다는 제한이다.
이렇게 Generic을 사용해서 접근하게 되면 위에서 생성한 2개를 수직으로 결합하기 위해서는
뒤집힌 삼각형과 일반 삼각형을 결합하는
JoinedShape<FlippedShape<Triangle>, Triangle>같은 구조체를 생성하게 된다.생성하는 도형에 대한 자세한 타입 정보를 노출하게되면 전체 반환 유형을 반드시 명시해야만 하기 때문에
ASCII 도형 모듈의 public 인터페이스에 포함되지 않는 타입들이 노출될 수 있다.
이때 실제로 모듈 내부의 코드는 다양한 방식으로 똑같은 도형을 만들 수 있으며,
모듈 외부에서 도형을 사용하는 코드는 변환 목록에 대해서 구현 세부 정보를 고려할 필요가 없다.
따라서 JoinedShape과 FlippedShape와 같은 Wrapper 타입들은 모듈의 사용자에게는 중요하지 않으므로 보여질 필요가 없다.
모듈의 public 인터페이스는 도형을 결합하고, 뒤집는 것과 같은 함수들로 구성되면 되고 이러한 함수는 다른 Shape 값을 반환하면 된다.
즉, 외부 사용자는 필요한 것만 알면되지 불필요한 걸 노출하지 말고 감출 수 있다면 감추는게 좋다.
Opaque Type의 반환
Opaque type은 제네릭 타입의 반대라고 생각할 수 있다.
제네릭 타입은 함수를 호출하는 코드에서 매개변수의 타입을 선택하고, 함수 구현에서 추상화된 방식으로 값을 반환할 수 있다.
예시는 아래와 같다.
max 함수를 호출하는 코드는 x, y의 타입에 따라서 T가 결정되게 된다.
이때 Comparable 프로토콜을 채택하는 모든 타입은 x, y의 타입이 될 수 있다.
함수의 내부 코드는 호출자가 제공하는 타입이 무엇이든 처리할 수 있도록 작성되게 된다.
따라서 max 함수의 구현은 모든 Comparable 타입이 공유하는 기능만 사용할 수 있게 된다.
그러나 만약 opaque type을 사용하게 되면 상황이 달라진다.
opaque type은 함수 구현에서 함수를 호출하는 코드에서 추상화 된 방식으로 반환되는 값의 타입을 선택할 수 있다.
아래 예시 함수는 기본 타입을 노출하는 것이 아니라 사다리꼴을 반환하게 된다.
⭐️ 이 부분을 보면 makeTrapezoid 함수는
some Shape를 반환 타입으로 선언하고, return 값으로 구체적인 타입을 지정하지 않고 Shape 프로토콜을 준수하는 특정 타입의 값을 반환하게 된다.이렇게 makeTrapezoid 함수를 작성하면 public interface의 부분으로 만들어지는 모양을 타입으로 지정하지 않고도 return 값이 Shape로 표현할 수 있게 된다.
이때 함수 내부 구현은 2개의 삼각형과 1개의 사각형을 사용하지만, return 값의 타입을 변경하지 않고도 여러 방법으로 사다리꼴을 구현하도록 수정할 수 있게 된다.
이 예시를 통해서 opaque type은 제네릭 타입과 반대인 것을 확인할 수 있다.
이런 opaque type은 제네릭과 결합될 수도 있다.
Shape 프로토콜을 준수하는 일부 타입의 값을 반환하는 예시를 보자
예제의
opaquedJoinedTriangle변수의 값은 전 섹션인 Opaque Type이 해결하는 문제 섹션에 제네릭 예제의opaquedJoinedTriangle와 동일하다.그러나 예제의 값과는 다르게 flip(), join()은 return 타입을 opaque type으로 반환하는 Shape을 타입으로 가지는 제네릭 함수이기 때문에 타입이 보여지지 않게 된다.
두 함수는 제네릭 타입에 의존하며, 함수의 파라미터가
FlippedShape,JoinedShape타입에 필요한 타입 정보를 전달하기 때문에 모두 제네릭이다.opaque type을 반환 값으로 가지는 함수가 여러 위치에서 반환하는 경우에는 모든 반환 값들이 동일한 타입을 가져야만 한다.
제네릭 함수의 경우에도 해당 반환 타입은 함수의 제너릭 타입 파라미터로 사용할 수 있지만 여전히 단일 타입이어야 한다.
아래의 예를 보자.
만약 위의 함수에 Square 타입을 매개변수로 호출한다면 Square를 호출할 것이고, 그렇지 않다면 FlippedShape를 호출할 것이다.
이렇게 작성된 함수는 단일 타입의 값을 반환해야한다는 요구 사항을 위반하고, 함수자체가 유효하지 않게 된다.
이 함수를 동작하게 하려면 FlippedShape를 수정하는 것이다.
아래와 같이 수정하면 위의 함수는 항상 FlippedShape 타입을 반환할 수 있게 된다.
항상 단일 타입을 반환해야한다고 해서, opaque 반환 타입에 제네릭 사용을 제한하는 것은 아니다.
이때는 T의 타입에 따라서 반환 값의 타입이 달라지게 된다.
매개변수로 전달되는 shape이 어떤 것이든지 상관없이 repeat 함수는 해당 shape를 생성해서 array 형태로 반환해준다.
그럼에도 불구하고 반환 값은 항상 동일한 기본 타입인
[T]이므로, opaque 반환 타입을 가진 함수는 단일 타입의 반환 값만 가져야한다는 요구사항을 만족하게 된다.opaque type과 protocol type의 차이점
함수의 반환 값으로 opaque type과 protocol type을 반환하는 것은 비슷해보이지만, 실제 타입 정체성을 유지하는지 여부가 다르다.
Opaque type은 하나의 특정 타입을 참조하지만 함수 호출자는 어떤 타입인지 볼 수 없는 것이다.
반면 protocol type은 protocol을 준수하는 모든 타입을 참조할 수 있다.
따라서 protocol type은 저장하는 값의 기본 타입에 대해 더 많은 유연성을 제공하고, opaque type은 기본 타입에 대한 더 강력한 보장을 해 줄 수 있다.
예시를 살펴보자.
protoFlip 함수는 flip과 동일한 함수 구현부를 가지며, 항상 같은 타입의 값을 반환한다.
flip과는 다르게 protoFlip이 반환하는 값은 항상 같은 타입을 가질 필요가 없으며 Shape 프로토콜을 준수하기만 하면된다.
즉, protoFlip은 flip이 만드는 것보다 느슨한 API 체결을 만들어준다. 따라서 여러 타입의 값을 반환하는 유연성을 가지게 된다.
이 경우에서도 매개변수로 들어오는
T의 종류에 따라서 Square 인스턴스나 FlippedShape 인스턴스를 반환해주게 된다.그리고 이 함수에 의해서 반환되는 2개의 Shpae 아예 다른 타입을 가진다.
즉, 이 함수의 다른 유효한 버전은 같은 모양의 여러 인스턴스를 뒤집을 때 다른 타입의 값을 반환할 수 있다는 것이다.
그렇기 때문에 protoFlip() 의 구체적이지 않은 반환 타입은 타입의 정보에 많이 의존해야하는 많은 함수들이 반환된 값을 사용할 수 없게된다.
따라서 위의 예시와 같이
==연산자를 사용할 수 없게 된다.그 이유는 Shape 프로토콜이
==연산자를 프로토콜의 요구 사항으로 가지고 있지 않기 때문에 발생하게 된다.그래서
==연산자를 추가하려고 하면 left-hand, right-hand의 타입을 알아야한다는 조건이 있기 때문에 다시 문제가 발생하게된다.이러한 종류의 연산자는 일반적으로 프로토콜을 채택하는 구체 타입과 일치하는
Self타입의 인수를 사용하지만, 프로토콜에Self요구사항을 추가하게되면 프로토콜을 타입으로 사용할 때 발생하는 type erasure를 허용하지 않게 된다.함수의 반환 타입으로 protocol type을 사용하면 protocol을 준수하는 모든 타입을 유연하게 반환할 수 있지만, 이러한 유연성의 대가는 반환된 값에 대해 일부 작업을 수행할 수 없게 되고 위 예제는
==연산자를 사용할 수 없음을 보여준다.또한 이 접근의 다른 문제점은 shape의 변형이 중첩되지 않는다는 것이다.
삼각형을 뒤집은 결과는 Shape 타입의 값이고, protoFlip() 은 Shape 프로토콜을 준수하는 어떤 타입의 값을 가진다.
그러나 이때 protocol type의 값은 protoFlip()이 반환하는 Shape를 준수하지 않으므로 프로토콜을 준수하지 않게 된다.
즉, 여러 번형을 적용하는
protoFlip(protoFlip(smallTriangle))과 같은 코드에서 뒤집힌 모양은 protoFlip()에 대해 유효하지 않은 인수이기 때문에 유효하지 않게 된다.반대로 opaque type은 기본 타입의 정체성을 보존하기 때문에 Swift는 연관된 타입을 유추할 수 있으므로 프로토콜 타입을 반환 값으로 사용할 수 없는 위치에 opaque type을 반환 값으로 사용할 수 있다. 예를 보자.
이러한 경우에는 연관 타입이 있기 때문에 함수의 반환 타입으로
Container를 사용할 수 없다.제네릭 타입이 무엇인지 추론할 수 있는 함수 바디 외부에 충분한 정보가 없으므로 제너릭 반환 타입의 제약 조건으로도 사용할 수 없다.
이때 반환 타입으로 opaque type인
some Container를 사용하면 함수는 Container를 반환하지만 Container의 타입 지정은 거부하기 때문에 원하는 API 협약을 맺을 수 있게 된다.마지막에 보듯이 twelve의 타입을 Int로 유추되며, 이것은 opaque type에 타입 추론이 동작함을 보여준다.
makeOpaqueContainer()의 구현부에서 opaque container의 기본 타입은[T]이다.이 경우에는
T는 Int 이므로 반환 값은 정수의 배열이고, 연관 타입인 Item의 타입은 Int로 유추된다.그리고 Container의 subscript는 Item을 반환하기 때문에 twelve의 타입도 Int로 유추된다는 것을 알 수 있다.