1. Opaque Types ๐Ÿ‘ฉโ€๐Ÿ’ป

์šฐ๋ฆฌ๋Š” ์ด๋ฏธ Generics ์—์„œ Opaque Types๋ฅผ ๋ณธ ์  ์žˆ๋‹ค. ํ•จ์ˆ˜ ๋˜๋Š” ๋ฉ”์„œ๋“œ์˜ return type ์„ Type์ด ์•„๋‹Œ some Type์œผ๋กœ ๋ฐ”๊ฟ” Type ์˜ ์ผ๋ถ€์ž„์„ ์•”์‹œํ•  ๋ฟ ๋ช…ํ™•ํ•œ Type ์ •๋ณด๋ฅผ ๊ฐ์ถ˜๋‹ค.

์ด๋ ‡๊ฒŒ ์ž์„ธํ•œ ์ •๋ณด๋ฅผ ๊ฐ์ถ”๋Š” ๊ฒƒ์€ ๋ชจ๋“ˆ๊ณผ ๋ชจ๋“ˆ์„ ํ˜ธ์ถœํ•˜๋Š” ์ฝ”๋“œ ์‚ฌ์ด์˜ ๊ฒฝ๊ณ„(boundaries)์—์„œ ์œ ์šฉํ•˜๋‹ค. Protocol Type ์˜ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ๊ณผ ๋‹ฌ๋ฆฌ Opaque Type ์€ Type Identity๋ฅผ ์œ ์ง€ํ•œ๋‹ค(Compiler ๋Š” Type ์˜ ์ •๋ณด์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ชจ๋“ˆ์˜ ํด๋ผ์ด์–ธํŠธ๋Š” ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค).


2. The Problem That Opaque Types Solve ๐Ÿ‘ฉโ€๐Ÿ’ป

1. Triangle

Opaque Types ๊ฐ€ ํ•ด๊ฒฐ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ๋ฅผ ์‚ดํŽด๋ณด๊ธฐ ์œ„ํ•ด ๊ธฐ์กด์˜ Nonopaque Types ๋ฒ„์ „์˜ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž. ๋‹ค์Œ์€ ASCII ๊ทธ๋ฆผ ๋ชจ์–‘์„ ๊ทธ๋ฆฌ๋Š” ๋ชจ๋“ˆ๋กœ์จ String ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” draw() ํ•จ์ˆ˜๋ฅผ ์š”๊ตฌ์‚ฌํ•ญ์œผ๋กœ ์ •์˜ํ•˜๋Š” Shape protocol ๊ณผ ์ด๊ฒƒ์„ ์ค€์ˆ˜ํ•˜๊ธฐ ์œ„ํ•œ Triangle structure ๋‹ค.

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(type(of: smallTriangle))  // Triangle
print(smallTriangle)            // Triangle(size: 3)

๊ทธ๋ฆฌ๊ณ  ์ด Triangle(size: 3)์ด draw() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด result ์—๋Š” ["*", "**", "***"]๊ฐ€ ๋‹ด๊ธฐ๊ฒŒ ๋  ๊ฒƒ์ด๋‹ค. draw() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์ „ joined(separator:) ๋ฉ”์„œ๋“œ์˜ ์ž‘๋™์„ ๋จผ์ € ์‚ดํŽด๋ณด์ž.

var arr = ["*", "**", "***"]
print(arr)                          // ["*", "**", "***"]
print(arr.joined())                 // ******
print(arr.joined(separator: ", "))  // *, **, ***

์ด์ œ ์–ด๋–ค ๊ทธ๋ฆผ์ด ๊ทธ๋ ค์งˆ์ง€ ์ƒ์ƒํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค. ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด ๊ทธ๋ฆผ์„ ๊ทธ๋ ค๋ณด์ž.

print(smallTriangle.draw())
*
**
***

*\n**\n***๊ฐ€ ์ €์žฅ๋˜์–ด ์œ„์™€ ๊ฐ™์ด ์ถœ๋ ฅ๋œ๋‹ค.

2. FlippedShape

struct FlippedShape<T: Shape>: Shape {
    var shape: T
    func draw() -> String {
        let lines = shape.draw().split(separator: "\n")
        return lines.reversed().joined(separator: "\n")
    }
}

Generic Types ๋ฅผ ์ด์šฉํ•ด FlippedShped Structure ๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์—ฌ๊ธฐ์—๋Š” ์ค‘์š”ํ•œ ์ œ์•ฝ์ด ์žˆ๋Š”๋ฐ, ๋’ค์ง‘ํžŒ ๊ฒฐ๊ณผ(flipped result)๋ฅผ ์ƒ์„ฑํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋œ Exact Generic Type ์„ ๋…ธ์ถœ(expose)์‹œํ‚จ๋‹ค.

let flippedTriangle = FlippedShape(shape: smallTriangle)
print(flippedTriangle.draw())
***
**
*

๋ชจ๋“ˆ ์‚ฌ์šฉ์ž๊ฐ€ ์•Œ์•„์•ผ ํ•˜๋Š” ๊ฒƒ์€ ๋ชจ๋“ˆ ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณต๋ฐ›๊ธฐ๋กœ ํ•œ Shape protocol ์˜ ๋ฌด์–ธ๊ฐ€ (์ด ๊ฒฝ์šฐ draw() ๋ฉ”์„œ๋“œ)๋ฟ์ด๋‹ค.
๊ทธ๋Ÿฐ๋ฐ Shape Protocol ์„ ์ค€์ˆ˜ํ•˜๋„๋ก draw()๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด Structure flippedTriangle ๋ฅผ ๊ทธ๋Œ€๋กœ ๋…ธ์ถœํ•˜๋ฉด ์—ฌ๊ธฐ ์‚ฌ์šฉ๋œ Wrapper ์ธ FlippedShape๊ฐ€ ๊ทธ๋Œ€๋กœ ๋…ธ์ถœ๋œ๋‹ค(= ๋’ค์ง‘ํžŒ ๊ฒฐ๊ณผ๋ฅผ ์ƒ์„ฑํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋œ Exact Generic Type ์„ ๋…ธ์ถœ์‹œํ‚จ๋‹ค).

print(flippedTriangle.shape)        // Triangle(size: 3)

Wrapper์˜ Exact Generic Type ์ด ๋…ธ์ถœ๋˜์–ด ๋ถˆํ•„์š”ํ•œ ์ •๋ณด(FlippedShape ์˜ โ€˜shapeโ€™ Property)๊ฐ€ ๋…ธ์ถœ๋œ๋‹ค.

3. JoinedShape

์ด๋ฒˆ์—๋Š” Shape Protocol ์„ ์ค€์ˆ˜ํ•˜๋Š” 2๊ฐœ์˜ shape ์„ ๊ฒฐํ•ฉํ•˜๋Š” Structure ๋ฅผ ์ •์˜ํ•ด๋ณด์ž.

struct JoinedShape<T: Shape, U: Shape>: Shape {
    var top: T
    var bottom: U
    func draw() -> String {
        top.draw() + "\n" + bottom.draw()
    }
}

JoinedShape<T: Shape, U: Shape> structure ๋Š” 2๊ฐœ์˜ shapes ๋ฅผ ์ˆ˜์ง์œผ๋กœ ๊ฒฐํ•ฉํ•œ๋‹ค.

์ด๊ฒƒ์€ ์•„๋ ˆ์˜ ์ฝ”๋“œ์™€ ๊ฐ™์ด Flipped Triangle ์„ Another Triangle ๊ณผ ๊ฒฐํ•ฉํ•ด JoinedShape<FlippedShape<Triangle>, Triangle>๊ณผ ๊ฐ™์€ return type ์„ ์ƒ์„ฑํ•œ๋‹ค.

let joinedTriangles = JoinedShape(top: smallTriangle, bottom: flippedTriangle)
print(joinedTriangles.draw())
*
**
***
***
**
*

shape ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ์ž์„ธํ•œ ์ •๋ณด๋ฅผ ๋…ธ์ถœํ•˜๋ฉด, Full Return Type์„ ๋ช…์‹œํ•ด์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ASCII ๊ทธ๋ฆผ ๋ชจ์–‘์„ ๊ทธ๋ฆฌ๋Š” ๋ชจ๋“ˆ์˜ public interface ์— ํฌํ•จ๋˜์ง€ ์•Š์€ Type ์ด ์œ ์ถœ๋  ์ˆ˜ ์žˆ๋‹ค.

print(joinedTriangles.top)          // Triangle(size: 3)
print(joinedTriangles.bottom)       // FlippedShape<Triangle>(shape: __lldb_expr_38.Triangle(size: 3))

๋ชจ๋“ˆ ๋‚ด์˜ ์ฝ”๋“œ๋Š” ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ฐ™์€ ๋ชจ์–‘์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ชจ๋“ˆ ์™ธ๋ถ€์˜ ๋‹ค๋ฅธ ์ฝ”๋“œ๋Š” ์ด ๋ชจ๋“ˆ์˜ ๊ตฌํ˜„ ๋ชฉ๋ก๊ณผ ๊ฐ™์€ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ์•Œ ํ•„์š”๊ฐ€ ์—†๋‹ค.

๋”ฐ๋ผ์„œ FlippedShape, JoinedShape ์™€ ๊ฐ™์€ Wrapper Types๋Š” ๋ชจ๋“ˆ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ค‘์š”ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ํ‘œ์‹œ๋˜์ง€ ์•Š์•„์•ผํ•œ๋‹ค. ๋ชจ๋“ˆ์˜ public interface ๋Š” shape ์„ ๊ฒฐํ•ฉํ•˜๊ฑฐ๋‚˜ ๋’ค์ง‘๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ์ž‘์—…์œผ๋กœ ๊ตฌ์„ฑ๋˜๋ฉฐ, ์ด๋Ÿฌํ•œ ์ž‘์—…์€ ๋˜ ๋‹ค๋ฅธ Shape ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.


3. Returning an Opaque Type ๐Ÿ‘ฉโ€๐Ÿ’ป

1. Square & makeTrapezoid()

Opaque Types ๋Š” Generic Types ์˜ ๋ฐ˜๋Œ€๋กœ ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๋‹ค.

Generic Types ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ํ•จ์ˆ˜๋Š” ์ถ”์ƒํ™”๋œ ๋ฐฉ์‹(abstracted away)์œผ๋กœ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, return type ์€ ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ๊ฒฐ์ •๋œ๋‹ค.

func max<T>(_ x: T, _ y: T) -> T where T: Comparable { ... }

max(_:_:) ํ•จ์ˆ˜๋Š” ํ˜ธ์ถœํ•˜๋Š” ์ฝ”๋“œ์˜ x, y ๊ฐ’์— ๋”ฐ๋ผ T์˜ Type ์ด ์ •ํ•ด์ง€๊ณ , ์ด T๋Š” Comparable protocol ์„ ์ค€์ˆ˜ํ•˜๋Š” ์–ด๋–ค Types ๋‚˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค.

๋ฐ˜๋ฉด Opaque Types ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜์˜ ๊ฒฝ์šฐ ์ด๋Ÿฌํ•œ ์—ญํ• ์ด ๋ฐ˜์ „๋œ๋‹ค. Opaque Types ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ฝ”๋“œ๋กœ๋ถ€ํ„ฐ ์ถ”์ƒํ™”๋œ ๋ฐฉ์‹์œผ๋กœ ํ•จ์ˆ˜์˜ ๊ตฌํ˜„์—์„œ return type ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค.

์œ„์—์„œ FlippedShape, JoinedShape ๋ฅผ ๊ทธ๋Œ€๋กœ ๋…ธ์ถœํ•ด ๋‹ค๋ฅธ ์ •๋ณด๊ฐ€ ๋…ธ์ถœ๋˜์—ˆ๋Š”๋ฐ Shape protocol ์ด ์ œ๊ณตํ•˜๊ธฐ๋กœ ์•ฝ์†ํ•œ draw()๋งŒ ๋…ธ์ถœ๋˜๋ฉด ๋˜๋ฏ€๋กœ

struct SomeStructure: Shape {
    func draw() -> String { something }
}

์™€ ๊ฐ™์ด FlippedShape, JoinedShape ๋กœ๋ถ€ํ„ฐ return type ์„ ์„ ํƒํ•ด ๋ถˆํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š๋Š” ๊ฐ„๋‹จํ•œ ํ˜•ํƒœ๋กœ Wrapping ๋œ ๊ฐ’์„ ์ œ๊ณตํ•ด์•ผํ•œ๋‹ค.

Opaque Types ๋ฅผ return type ์œผ๋กœ ์ •์˜ํ•  ๋•Œ ๊ฐ€๋Šฅํ•œ Types ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
An 'opaque' type must specify only 'Any', 'AnyObject', protocols, and/or a base class


๋‹ค์Œ ์˜ˆ์ œ๋ฅผ ์œ„ํ•ด ์‚ฌ๊ฐํ˜•์„ ๊ทธ๋ฆฌ๋Š” Square structure ๋ฅผ ์ถ”๊ฐ€๋กœ ์ •์˜ํ•˜์ž.

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")
    }
}

๋‹ค์Œ ์˜ˆ์ œ์—์„œ ํ•จ์ˆ˜ makeTrapezoid()๋Š” shape ์˜ ๋ช…ํ™•ํ•œ Type ์—†์ด ์‚ฌ๋‹ค๋ฆฌ๊ผด(trapezoid)์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
(์‚ฌ์šฉ์ž์—๊ฒŒ Triangle, Square, FlippedShape, JoinedShape ์˜ Exact Generic Type ์ด ๋…ธ์ถœ๋˜์ง€ ์•Š๋Š”๋‹ค.)

func makeTrapezoid() -> some Shape {
    let top = Triangle(size: 2)
    let middle = Square(size: 2)
    let bottom = FlippedShape(shape: top)
    let trapezoid = JoinedShape(
        top: top,
        bottom: JoinedShape(top: middle, bottom: bottom)
    )
    return trapezoid
}
let trapezoid = makeTrapezoid()
print(trapezoid.draw())
*
**
**
**
**
*


๊ทธ๋ ‡๋‹ค๋ฉด Nonopaque Types ์—์„œ ์ •์˜ํ•œ JoinedShape ์™€ ๋ญ๊ฐ€ ๋‹ค๋ฅผ๊นŒ? ํ•œ๋ฒˆ ๋น„๊ตํ•ด๋ณด๋„๋ก ํ•˜์ž.

Nonopaque Return Type

print(joinedTriangles.top)      // Triangle(size: 3)
print(joinedTriangles.bottom)   // FlippedShape<Triangle>(shape: __lldb_expr_38.Triangle(size: 3))

๋ชจ๋“ˆ์˜ ์‚ฌ์šฉ์ž๋Š” draw()์˜ ๊ฒฐ๊ณผ๋งŒ ์•Œ๋ฉด ๋œ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ JoinedShape๋Š” Shape Protocol ์„ ์ค€์ˆ˜ํ•˜๋Š” Structure ์ž์ฒด๋ฅผ ์ •์˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋œ Exact Generic Type JoinedShape๊ฐ€ ๋…ธ์ถœ๋˜์–ด ์ด๊ฒƒ์ด ๊ฐ–๋Š” top๊ณผ bottom์— ๋Œ€ํ•œ ์ •๋ณด๊นŒ์ง€ ๋…ธ์ถœ์‹œํ‚จ๋‹ค. ์œ„์—์„œ๋„ ์ด๋ฏธ ์„ค๋ช…ํ–ˆ๋“ฏ์ด FlippedShape, JoinedShape ์™€ ๊ฐ™์€ Wrapper Types๋Š” ๋ชจ๋“ˆ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ค‘์š”ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ํ‘œ์‹œ๋˜์ง€ ์•Š์•„์•ผํ•˜๋Š”๋ฐ Structure ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ถˆํ•„์š”ํ•œ ์ •๋ณด๊ฐ€ ๋…ธ์ถœ๋œ๋‹ค.


Before Opaque Return Type

makeTrapezoid() ์—ญ์‹œ ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ๋Š” JoinedShape๊ฐ€ ์ƒ์„ฑํ•œ Structure ๋กœ๋ถ€ํ„ฐ top๊ณผ bottom์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ

Opaque Return Type 1

๋ฐ˜ํ™˜๋œ ๊ฐ’์—์„œ๋Š” ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค. makeTrapezoid()๋Š” Return Type ์„ Opaque Type ์œผ๋กœ Wrappingํ•ด Shape protocol ์„ ์ค€์ˆ˜ํ•˜๋Š” ๊ฐ์ฒด์˜ ๋‹ค๋ฅธ ์ •๋ณด๋ฅผ ๋…ธ์ถœ์‹œํ‚ค์ง€ ์•Š๊ณ  ๋ชจ๋“ˆ์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์•Œ์•„์•ผ ํ•˜๋Š” draw()๋งŒ ๋…ธ์ถœ์‹œํ‚จ๋‹ค.

2. flip(_:) & join(_:_:) with Generics

์œ„์—์„œ makeTrapezoid() ํ•จ์ˆ˜๋Š” shape ์˜ ๋ช…ํ™•ํ•œ Type ์—†์ด some Shape๋ฅผ ๋ฐ˜ํ™˜ํ–ˆ๋‹ค. ์ฆ‰, Shape protocol ์„ ์ค€์ˆ˜ํ•˜๋Š” Structures ์˜ Exact Generic Type ๋Œ€์‹ 

struct SomeStructure: Shape {
    func draw() -> String { something }
}

ํ˜•ํƒœ๋กœ Wrappingํ•ด ๋ฐ˜ํ™˜ํ–ˆ๋‹ค.

An 'opaque' type must specify only 'Any', 'AnyObject', protocols, and/or a base class๋ฅผ ๋‹ค์‹œ ํ•œ ๋ฒˆ ๋” ๋– ์˜ฌ๋ ค๋ณด์ž.

  • Generic Types ๊ฐ€ ํ•ด๊ฒฐํ•˜๋Š” ๋ฌธ์ œ๋Š” ๋™์ผํ•œ body ๋ฅผ ๊ฐ–๋Š” ์—ฌ๋Ÿฌ cases ๋ฅผ Type Inference ๋ฅผ ์‚ฌ์šฉํ•ด ํ•˜๋‚˜์˜ ์ •์˜๋กœ ์žฌ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ์ฝ”๋“œ์˜ ์ค‘๋ณต์„ ์ตœ์†Œํ™”ํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์œ ์—ฐํ•˜๊ฒŒ ๋งŒ๋“ค์—ˆ๋‹ค.
  • Opaque Types ๊ฐ€ ํ•ด๊ฒฐํ•˜๋Š” ๋ฌธ์ œ๋Š” Types ์˜ ๋ถˆํ•„์š”ํ•œ ์ •๋ณด ๋…ธ์ถœ์„ ๋ฐฉ์ง€(hiding)ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ํŠน์ • Type์„ ๋ฐ˜ํ™˜ํ•˜๋”๋ผ๋„ ์œ„์™€ ๊ฐ™์ด ๊ทธ Type Object ๋‚ด์—์„œ ๋ฐ˜ํ™˜ ํ•˜๋ ค๋Š” ๋‹จ์ผ Type Member๋งŒ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผํ•œ๋‹ค. ์ด๊ฒƒ์€ ์ถ”์ƒ์ ์ธ ํ•ฉ์˜์˜ ๊ฒฐ๊ณผ๋ผ ๋ณผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด ๋ชจ๋“ˆ์„ ๊ฐœ๋ฐœํ•˜๋Š” ๊ฐœ๋ฐœ์ž์™€ Compiler ๋งŒ Type Object๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ์ด ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ๋Š” ๋‹จ์ง€ ๋งค๋ฒˆ ๋™์ผํ•œ Type Member๋ฅผ ์–ป๋Š”๋‹ค๋Š” ๊ฒƒ๋งŒ ์•Œ๊ณ  ์žˆ์œผ๋ฉด ๋˜๊ณ , ๋งค๋ฒˆ ๋™์ผํ•œ Identity ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋‹ˆ ํด๋ผ์ด์–ธํŠธ๋Š” ์ด return type ์„ ๋”์šฑ ์‹ ๋ขฐํ•˜๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

Return Type ์œผ๋กœ Opaque Types๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ์—ฌ๋Ÿฌ ์œ„์น˜์—์„œ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒฝ์šฐ, ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ์˜ ๋ชจ๋“  Return Values ์˜ Type ์€ ๋™์ผํ•ด์•ผํ•œ๋‹ค(all of the possible return values must have the same type).

์ด๊ฒƒ์€ Generic Functions ์˜ ๊ฒฝ์šฐ Return Type ์— Generic Types ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  Return Type some Type์€ ์—ฌ์ „ํžˆ Single Type ์ด์–ด์•ผ ํ•จ์„ ์˜๋ฏธํ•œ๋‹ค.

Opaque Types some Shape๋ฅผ return type ์œผ๋กœ ๊ฐ–๋Š” flip(_:), join(_:) ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€๋กœ ๊ตฌํ˜„ํ•ด๋ณด์ž. ์ด๋ฒˆ์—๋Š” Generics๋ฅผ ๊ฒฐํ•ฉํ•ด๋„ Opaque Types ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•ด๋ณธ๋‹ค.

func flip<T: Shape>(_ shape: T) -> some Shape {
    FlippedShape(shape: shape)
}

func join<T: Shape, U: Shape>(_ top: T, _ bottom: U) -> some Shape {
    JoinedShape(top: top, bottom: bottom)
}
let smallTriangle = Triangle(size: 3)
let opaqueJoinedTriangles = join(smallTriangle, flip(smallTriangle))
print(opaqueJoinedTriangles.draw())
*
**
***
***
**
*

Opaque Return Type 2

flip(_:)๊ณผ join(_:)์— ์˜ํ•ด ๋ฐ˜ํ™˜๋œ opaqueJoinedTriangles ์—ญ์‹œ draw() ์™ธ์—๋Š” ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค.

3. invalidFlip(_:)

์œ„์—์„œ Opaque Type ์˜ return type ์€ Single Type์ด์–ด์•ผ ํ•œ๋‹ค๊ณ  ํ–ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋ฒˆ์—๋Š” ์ด ์š”๊ตฌ์‚ฌํ•ญ์„ ์œ„๋ฐ˜ํ•˜๋Š” ์ž˜๋ชป๋œ case ๋ฅผ ์‚ดํŽด๋ณธ๋‹ค.

์œ„ flip(_:) ํ•จ์ˆ˜๋ฅผ ๋ณด๋ฉด ๊ตณ์ด ์ •์‚ฌ๊ฐํ˜•์„ ์ •์˜ํ•˜๋Š” Square๋Š” ๋’ค์ง‘์ง€ ์•Š์•„๋„ ๋  ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋ž˜์„œ flip(_:) ํ•จ์ˆ˜ ์•ˆ์—์„œ ์ „๋‹ฌ๋œ Shape ์˜ Type ์ด Square ์ผ ๊ฒฝ์šฐ ๊ทธ๋ƒฅ ๋ฐ˜ํ™˜ํ•˜๊ณ , ๊ทธ๋ ‡์ง€ ์•Š์„ ๊ฒฝ์šฐ์—๋งŒ ๋’ค์ง‘๋Š” ๊ฒƒ์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด ๋” ์ข‹์„๊ฑฐ๋ผ ํŒ๋‹จ๋˜์–ด ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.

Invalid Opaque Type

Opaque Type ์„ ๋ฐ˜ํ™˜ํ•˜๊ฒ ๋‹ค ํ•ด๋†“๊ณ  Single Type์ด ์•„๋‹Œ 2๊ฐ€์ง€ Types ๋กœ return ์„ ํ•˜๋ ค๊ณ  ํ•˜์ž Compiler ๊ฐ€ Opaque Type ์˜ ์š”๊ตฌ์‚ฌํ•ญ์— ์œ„๋ฐ˜๋จ์„ ์ธ์ง€ํ•˜๊ณ  ์—๋Ÿฌ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค.

  • Function declares an opaque return type โ€˜some Shapeโ€™, but the return statements in its body do not have matching underlying types


์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๋Š”

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")
    }
}

Square: Shape ๋ผ๋Š” ํŠน์ˆ˜ํ•œ ๊ฒฝ์šฐ๋ฅผ

struct FlippedShape<T: Shape>: Shape {
    var shape: T
    func draw() -> String {
        let lines = shape.draw().split(separator: "\n")
        return lines.reversed().joined(separator: "\n")
    }
}

FlippedShape: Shape์˜ ๋‚ด๋ถ€๋กœ ์˜ฎ๊ฒจ invalidFlip(_:) ํ•จ์ˆ˜๊ฐ€ ์–ธ์ œ๋‚˜ FlippedShape ์˜ some Shape ๋ฅผ returnํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

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")
    }
}


๋ณ€๊ฒฝ๋œ ์ฝ”๋“œ๋ฅผ ๋ชจ์•„ ๋น„๊ตํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • flip(_:) & join(_:_:)
struct FlippedShape<T: Shape>: Shape {
    var shape: T
    func draw() -> String {
        let lines = shape.draw().split(separator: "\n")
        return lines.reversed().joined(separator: "\n")
    }
}

func flip<T: Shape>(_ shape: T) -> some Shape {
    FlippedShape(shape: shape)
}
let smallTriangle = Triangle(size: 2)
let smallSquare = Square(size: 2)
let trapezoid = join(smallTriangle, join(smallSquare, flip(smallTriangle)))

print(type(of: trapezoid))  // JoinedShape<Triangle, JoinedShape<Square, FlippedShape<Triangle>>>
print(trapezoid.draw())
*
**
**
**
**
*


  • fixedInvalidFlip(_:)
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")
    }
}

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
}

// ๋”ฐ๋ผ์„œ ์œ„ `invalidFlip(_:)`์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฐ”๋€” ์ˆ˜ ์žˆ๋‹ค.
func fixedInvalidFlip<T: Shape>(_ shape: T) -> some Shape {
    return FlippedShape(shape: shape) // Error: return types don't match
}
let smallTriangle = Triangle(size: 2)
let smallSquare = Square(size: 2)
let trapezoid = join(smallTriangle, join(smallSquare, fixedInvalidFlip(smallTriangle)))

print(type(of: trapezoid))  // JoinedShape<Triangle, JoinedShape<Square, FlippedShape<Triangle>>>
print(trapezoid.draw())
*
**
**
**
**
*

4. repeat(shape:count:) - Opaque Return Types with Generics

ํ•ญ์ƒ Single Type์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ํ•ด์„œ Opaque Types๋ฅผ return ํ•˜๋Š” ํ•จ์ˆ˜์— Generic Types ์˜ ์‚ฌ์šฉ์„ ๋ง‰์ง€๋Š” ์•Š๋Š”๋‹ค. ๋‹ค์Œ์€ Generic Types ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ Opaque Types์˜ ์š”๊ตฌ์‚ฌํ•ญ์„ ๋งŒ์กฑํ•˜๋Š” ๊ฒฝ์šฐ๋ฅผ ๋ณด์ž.

์œ„์—์„œ invalidFlip(_:)ํ•จ์ˆ˜๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ–ˆ๋˜ ์ด์œ ๋Š”

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
}

๋Š” T๋ฅผ ๋ฐ›์•„ Square ๋˜๋Š” FlippedShape๋ผ๋Š” 2๊ฐ€์ง€ Types ๋กœ ๋ฐ˜ํ™˜ํ•˜๋ ค ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋ฐ˜๋ฉด

func `repeat`<T: Shape>(_ shape: T, count: Int) -> some Collection {
    Array<T>(repeating: shape, count: count)
}

repeat(shape:count:) ํ•จ์ˆ˜ ์—ญ์‹œ T์— ์˜์กดํ•˜๋ฏ€๋กœ ๋ฐ›๋Š” T์— ๋”ฐ๋ผ ๋ฐ˜ํ™˜๋˜๋Š” T์˜ Type ์€ ๋ณ€๊ฒฝ๋˜์ง€๋งŒ, some Collection์˜ ์ผ๋ถ€๋กœ์จ Array<T>๋ผ๋Š” Single Type ์œผ๋กœ Wrapping ๋œ Type ์„ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— Opaque Type ์˜ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ค€์ˆ˜ํ•œ๋‹ค.


์ด๋Š” flip(_:) & join(_:_:) ํ•จ์ˆ˜

func flip<T: Shape>(_ shape: T) -> some Shape {
    FlippedShape(shape: shape)
}

func join<T: Shape, U: Shape>(_ top: T, _ bottom: U) -> some Shape {
    JoinedShape(top: top, bottom: bottom)
}

์˜ some Shape๊ฐ€ ๊ฐ๊ฐ

struct SomeStructure: Shape {
    func draw() -> String { something }
}

๋ผ๋Š” Single Type ์œผ๋กœ Wrapping๋˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.


์ž˜ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•ด๋ณด์ž.

let doubleTriangle = `repeat`(smallTriangle, count: 2)
doubleTriangle.forEach { shape in
    if let shape = shape as? Shape {
        print(shape.draw())
    }
}
*
**
***
*
**
***
let tripleSquare = `repeat`(smallSquare, count: 3)
tripleSquare.forEach { shape in
    if let shape = shape as? Shape {
        print(shape.draw())
    }
}
***
***
***
***
***
***
***
***
***

Opaque Return Types ๋ฅผ Generic ๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด flip(_:) & join(_:_:) ์ฒ˜๋Ÿผ Return Type ์„ Single Type์œผ๋กœ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ํ•˜๋‚˜์˜ Type ์ด ๋‹ค๋ฅธ Types ๋ฅผ ํฌํ•จํ•˜๋„๋ก ๋งŒ๋“ค ํ•„์š” ์—†์ด some Type์„ Generic์„ ์‚ฌ์šฉํ•ด ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ๊ฐ๊ฐ์˜ ์ฝ”๋“œ๋ฅผ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.


4. Differences Between Opaque Types and Protocol Types ๐Ÿ‘ฉโ€๐Ÿ’ป

1. Opaque Types Preserve Type Identity

ํ•จ์ˆ˜์˜ return type ์ด Opaque Types ์ธ ๊ฒฝ์šฐ์™€ Protocol Types ์ธ ๊ฒฝ์šฐ๋Š” ์œ ์‚ฌํ•ด ๋ณด์ด์ง€๋งŒ ๋ช…ํ™•ํ•œ ์ฐจ์ด์ ๊ณผ ์„œ๋กœ๊ฐ€ ํ•ด๊ฒฐํ•˜๋Š” ๋ฌธ์ œ(์‚ฌ์šฉํ•จ์œผ๋กœ์จ ์–ป๋Š” ๊ฐ•์ )์ด ๋ช…ํ™•ํžˆ ๋‹ค๋ฅด๋‹ค. ์ด๋ฅผ ์ •๋ฆฌํ•ด๋ณด์ž.

  • Opaque Types : ๋ชจ๋“ˆ์˜ ํด๋ผ์ด์–ธํŠธ๊ฐ€ Types ์˜ ์ •๋ณด์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค(hiding). Single Type Identity ๋ฅผ ์œ ์ง€ํ•œ๋‹ค. Opaque Type ์€ ํ•˜๋‚˜์˜ ํŠน์ • Type ์„ ์ฐธ์กฐํ•˜์ง€๋งŒ, ํ•จ์ˆ˜ ํ˜ธ์ถœ์ž๋Š” ์–ด๋–ค Type ์ธ์ง€ ์•Œ ์ˆ˜ ์—†๋‹ค.
  • Protocol Types : ๋ชจ๋“ˆ์˜ ํด๋ผ์ด์–ธํŠธ๊ฐ€ Types ์˜ ์ •๋ณด์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค. Protocols ์„ ์ค€์ˆ˜ํ•˜๋Š” ๋ชจ๋“  Types ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ Type Identity ๊ฐ€ ์œ ๋™์ ์ด๋‹ค.

2. Strength of Opaque Types and Protocol Types

๋”ฐ๋ผ์„œ ๊ฐ Types ๊ฐ€ ๊ฐ•์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • Opaque Types

some Type์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด Wrapping ๋˜์–ด ๋ฐ˜ํ•œ๋˜๋Š” ๋ชจ์–‘์„ ๋ณด์ž.

struct SomeStructure: Shape {
    func draw() -> String { something }
}

Types ์˜ ์ •๋ณด๋ฅผ ์€๋‹‰ํ™”(hiding)ํ•  ์ˆ˜ ์žˆ์„ ๋ฟ ์•„๋‹ˆ๋ผ ํŠน์ • Protocols ๋ฅผ ์ค€์ˆ˜ํ•˜๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ๋ชจ๋“ˆ์ด ์–ด๋–ค Hierarchy ๊ตฌ์กฐ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋“ , ์ค‘๊ฐ„์— ๋ชจ๋“ˆ ๋‚ด๋ถ€๊ฐ€ ์–ด๋–ป๊ฒŒ ๋ณ€๊ฒฝ๋˜๋“  ์–ธ์ œ๋‚˜ one specific type์„ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ํ•จ์ˆ˜ ํ˜ธ์ถœ์ž ์ž…์žฅ์—์„œ ๋ณด๋ฉด ์ด๊ฒƒ์€ return type ์— ๋Œ€ํ•œ ๊ฐ•๋ ฅํ•œ ๋ณด์ฆ์„ ์•ฝ์†(Opaque Type ์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด ๋‹จ์ผ Identity ๋ฅผ ์œ ์ง€ํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผํ•˜๋ฏ€๋กœ)ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.


  • Protocol Types

ํŠน์ • Protocols ๋ฅผ ์ค€์ˆ˜ํ•˜๋ฉด ์–ด๋–ค Types ๋“  ๋ชจ๋‘ ํ—ˆ์šฉ๋จ์„ ์˜๋ฏธํ•œ๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ Types ์˜ ์ •๋ณด์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ ํ•จ์ˆ˜ ํ˜ธ์ถœ์ž ์ž…์žฅ์—์„œ ๋ณด๋ฉด ์ด๊ฒƒ์€ ๋†’์€ ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•˜๊ณ  Original Types ์— ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•œ๋‹ค.

3. Protocol Return Type give more Flexibility - protocolFlip(_:)

์œ„์—์„œ ์–ธ๊ธ‰ํ•œ Protocol Types ์˜ ๊ฐ•์ ์ธ ์ฝ”๋“œ๋ฅผ ์œ ์—ฐํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ๊ฒ€์ฆํ•ด๋ณธ๋‹ค. ์šฐ๋ฆฌ๋Š” ์œ„์—์„œ invalidFlip ์˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Square ์˜ ํŠน์ˆ˜ํ•œ ๊ฒฝ์šฐ๋ฅผ FlippedShape ์˜ ๋‚ด๋ถ€๋กœ ์˜ฎ๊ฒผ๋‹ค.

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 ๋‚˜ FlippedShape ์˜ ์ˆ˜์ • ์—†์ด return type ์„ Protocol Types๋กœ ๋ณ€๊ฒฝํ•ด๋ณด์ž.

func protocolFlip<T: Shape>(_ shape: T) -> Shape {
    if shape is Square {
        return shape
    }
    
    return FlippedShape(shape: shape)
}
let smallTriangle = Triangle(size: 2)
let smallSquare = Square(size: 2)
let trapezoid = join(smallTriangle, join(smallSquare, protocolFlip(smallTriangle)))

print(type(of: trapezoid))  // JoinedShape<Triangle, JoinedShape<Square, FlippedShape<Triangle>>>
print(trapezoid.draw())
*
**
**
**
**
*

Protocol Return Type ์€ ๋†’์€ ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•ด protocolFlip(_:)ํ•จ์ˆ˜๊ฐ€ Shape์™€ FlippedShape๋ผ๋Š” ๋‹ค๋ฅธ Types ๋ฅผ return ํ•˜๋”๋ผ๋„ Shape protocols ์„ ์ค€์ˆ˜ํ•œ๋‹ค๋ฉด ์ด๋ฅผ ํ—ˆ์šฉํ•œ๋‹ค.

4. Protocol Return Type cannot use Operations that depend on Type Information

ํ•˜์ง€๋งŒ Protocol Return Type์„ ์‚ฌ์šฉํ•  ๋•Œ ์œ ์˜ํ•ด์•ผํ•  ์ ์ด ์žˆ๋‹ค. ์ฝ”๋“œ๋ฅผ ์œ ์—ฐํ•˜๊ฒŒ ํ•ด์คŒ์œผ๋กœ์จ ๋งŽ์€ ์žฅ์ ์„ ๊ฐ–๋Š” ๊ฒƒ์€ ๋งž์ง€๋งŒ ๋ฐ˜๋Œ€๋กœ ๋งํ•˜๋ฉด, ์œ„ protocolFlip(_:)์˜ return type ์€ 2๊ฐœ์˜ ์™„์ „ํžˆ ๋‹ค๋ฅธ Types๋ฅผ ๊ฐ–๋Š”๋‹ค.

๋”ฐ๋ผ์„œ Type ์ •๋ณด์— ์˜์กดํ•˜๋Š” ๋งŽ์€ ์ž‘์—…์ด ๋ฐ˜ํ™˜๋œ ๊ฐ’์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Œ์„ ์˜๋ฏธํ•œ๋‹ค.

Triangle ๊ณผ FlippedShape ์— Equatable์„ ์ถ”๊ฐ€ํ•ด๋ณด์ž.

extension Triangle: Equatable {}
extension FlippedShape: Equatable where T == Triangle {
    static func == (lhs: FlippedShape<T>, rhs: FlippedShape<T>) -> Bool {
        lhs.shape == rhs.shape
    }
}

์ด์ œ Triangle ๊ณผ FlippedShape ์€ == operator ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.


1 ) Returning Opaque Types

let smallTriangle = Triangle(size: 3)
let anotherSmallTriangle = Triangle(size: 3)
print(smallTriangle == anotherSmallTriangle)      // true

let flippedTriangle = FlippedShape(shape: smallTriangle)
let anotherFlippedTriangle = FlippedShape(shape: smallTriangle)
print(flippedTriangle == anotherFlippedTriangle)  // true


2 ) Returning Protocol Types

let protocolFlippedTriangleA = protocolFlip(smallTriangle)
let protocolFlippedTriangleB = protocolFlip(smallTriangle)

print(type(of: flippedTriangle))            // FlippedShape<Triangle>
print(type(of: protocolFlippedTriangleA))   // FlippedShape<Triangle>

์šฐ์„  Initializer ์— ์˜ํ•ด ์ƒ์„ฑ๋œ flippedTriangle๊ณผ Protocol Return Type์— ์˜ํ•ด ๋ฐ˜ํ™˜๋œ protocolFlippedTriangleA์€ ๋‘˜ ๋‹ค ๋™์ผํ•œ FlippedShape<Triangle> Type ์ž„์ด ํ™•์ธ๋œ๋‹ค.

print(protocolFlippedTriangleA == protocolFlippedTriangleB) // error: Binary operator '==' cannot be applied to two 'any Shape' operands

ํ•˜์ง€๋งŒ Protocol Return Type์€ == operator ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์–ด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

5. Downcasting Protocol Return Types

๋งŒ์•ฝ ์œ„ ๊ฒฝ์šฐ Protocols ๋ฅผ ์ด์šฉํ•œ ์œ ์—ฐ์„ฑ์˜ ์žฅ์ ์„ ํ™œ์šฉํ•˜๋ฉด์„œ, Types ์˜ ์ •๋ณด๋ฅผ ํ™œ์šฉํ•˜๊ณ ์ž ํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ?

์ž ์‹œ ๋‹ค๋ฅธ ์–ธ์–ด์˜ ์ด์•ผ๊ธฐ๋ฅผ ์‚ดํŽด๋ณด์ž. ๋งŒ์•ฝ Java ์™€ ๊ฐ™์€ ์–ธ์–ด๋ฅผ ํ•ด๋ดค๋‹ค๋ฉด ์–ด๋–ค ํ•จ์ˆ˜์˜ return ๊ฐ’์„ ๋ฐ›์•„ ๋ณ€์ˆ˜์— ํ• ๋‹นํ•  ๋•Œ ArrayList<String>, LinkedList<String>์™€ ๊ฐ™์ด ๋ช…ํ™•ํ•œ Types ๋ฅผ ์„ ์–ธํ•ด ๋ฐ›์ง€ ์•Š๊ณ , Interface ๋ฅผ ์ด์šฉํ•ด List<String>์œผ๋กœ ๋ฐ›๋„๋ก ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

List<String> result = someFunction()  // return `ArrayList<String>` or `LinkedList<String>` or anything adopt to 'List' interface. 

์ด๋Š” ์ด ํฌ์ŠคํŒ…์„ ์‹œ์ž‘ํ•  ๋•Œ ์„ค๋ช…ํ–ˆ๋˜ ์ž์„ธํ•œ ์ •๋ณด๋ฅผ ๊ฐ์ถ”๋Š” ๊ฒƒ์€ '๋ชจ๋“ˆ'๊ณผ '๋ชจ๋“ˆ์„ ํ˜ธ์ถœํ•˜๋Š” ์ฝ”๋“œ' ์‚ฌ์ด์˜ '๊ฒฝ๊ณ„(boundaries)'์—์„œ ์œ ์šฉํ•˜๋‹ค๋Š” ์„ค๋ช…๊ณผ ์œ ์‚ฌํ•จ์„ ๋ณด์—ฌ์ค€๋‹ค.

์ด๋ ‡๊ฒŒ boundaries ์—์„œ ์œ ์—ฐ์„ฑ์„ ํ™•๋ณดํ•˜๋Š” ๋Œ€์‹  result๋Š” List ๊ฐ€ ๊ณตํ†ต์œผ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋‚˜, ArrayList ๋‚˜ LinkedList etc...๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์ „์šฉ ๋ฉ”์„œ๋“œ๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค. ๋งŒ์•ฝ, ์ „์šฉ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Downcasting์„ ํ•ด์•ผํ•œ๋‹ค.


๋‹ค์‹œ Swift ๋กœ ๋Œ์•„์™€๋ณด์ž. flippedTriangle์™€ protocolFlippedTriangleA์€ ๋™์ผํ•œ Type ์ด์ง€๋งŒ Protocol Return Type์— ์˜ํ•ด ๋ฐ˜ํ™˜๋œ protocolFlippedTriangleA ๋งŒ == operator ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์—ˆ๋‹ค. ํ•œ ๋ฒˆ ์ด๊ฒƒ์„ Downcasting ํ•ด๋ณด์ž.

let downcastedFlippedTriangleA = protocolFlippedTriangleA as? FlippedShape<Triangle>
let downcastedFlippedTriangleB = protocolFlippedTriangleB as? FlippedShape<Triangle>

print(downcastedFlippedTriangleA == downcastedFlippedTriangleB) // true

์ž‘๋™๋œ๋‹คโ€ผ๏ธ

6. Protocol Has an Associated Type Cannot Use as the Return Types

๋‹ค์Œ์€ Generics ์—์„œ Array ์— ์‚ฌ์šฉ์ž๊ฐ€ ์ƒ์„ฑํ•œ Container ๋ผ๋Š” Custom Protocol ์— ๋Œ€ํ•œ ์ ํ•ฉ์„ฑ์„ ์ค€์ˆ˜ํ•˜๋„๋ก ํ•œ ์ฝ”๋“œ์˜ ์ผ๋ถ€๋‹ค.

protocol IntContainer {
    mutating func append(_ item: Int)
    var count: Int { get }
    subscript(i: Int) -> Int { get }
}

protocol StringContainer {
    mutating func append(_ item: String)
    var count: Int { get }
    subscript(i: Int) -> String { get }
}

์šฐ๋ฆฌ๋Š” ์œ„์™€ ๊ฐ™์€ ์—ฌ๋Ÿฌ Types ์— ๋Œ€ํ•œ ๋ฒ„์ „์˜ Container ๋ฅผ ํ•˜๋‚˜์˜ ์ •์˜๋กœ ์žฌ์‚ฌ์šฉํ•˜๊ณ ์ž Associated Types ๋ฅผ ์‚ฌ์šฉํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜ํ–ˆ์—ˆ๋‹ค.

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

๊ทธ๋ฆฌ๊ณ  Array ๋Š” ์ด๋ฏธ ์œ„์™€ ๊ฐ™์€ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ค€์ˆ˜ํ•˜๊ธฐ ์œ„ํ•œ ๊ตฌํ˜„์ด ์ด๋ฏธ ์กด์žฌํ•˜๋ฏ€๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ ํ•ฉ์„ฑ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

extension Array: Container { }


์šฐ์„  Protocols ๊ฐ€ Protocol Return Type ์œผ๋กœ ์‚ฌ์šฉ๋  ๋•Œ์˜ ๊ฒฝ์šฐ๋ฅผ ์‚ดํŽด๋ณด๊ธฐ ์œ„ํ•ด Container Protocol ์˜ ์š”๊ตฌ์‚ฌํ•ญ์„ ๋ชจ๋‘ ์ œ๊ฑฐํ•ด๋ณด์ž.

protocol Container { }
extension Array: Container { }
func makeProtocolContainer<T, C: Container>(item: T) -> C {
    [item]  // error: Cannot convert return expression of type '[T]' to return type 'C'
}

item ์ด๋ผ๋Š” ๋ฌด์–ธ๊ฐ€๋ฅผ ๋ฐ›์•„ Array()์— ์ €์žฅํ•ด ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋‹ค. ์šฐ๋ฆฌ๋Š” ์œ„์—์„œ Array ๊ฐ€ Container Protocol ์„ ์ค€์ˆ˜ํ•˜๋„๋ก ํ–ˆ์œผ๋ฏ€๋กœ ์ด๋ฅผ Generic Types ๋กœ ์ •์˜ํ•ด ๋ฐ˜ํ™˜ํ•˜๊ณ ์ž ํ–ˆ๋‹ค. ์‹ค์ œ๋กœ Container Protocol ์€ ์•„๋ฌด๋Ÿฐ ์š”๊ตฌ์‚ฌํ•ญ์ด ์—†์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  Swift compiler ๋Š” Generic Type T ๋ฅผ Container Protocol ์„ ์ค€์ˆ˜ํ•˜๋Š” Generic Type C ๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์—†๋‹ค๊ณ  ์ด์•ผ๊ธฐํ•œ๋‹ค.

T๋„ Type Inference ๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, C๋„ Type Inference ๊ฐ€ ํ•„์š”ํ•œ ์ƒํ™ฉ์ด๋‹ค. Swift ๋Š” ์‚ฌ์ „์— T ์— ๋Œ€ํ•œ ์ถฉ๋ถ„ํ•œ ์ •๋ณด๋„, C ์— ๋Œ€ํ•œ ์ถฉ๋ถ„ํ•œ ์ •๋ณด๋„, ๊ฒŒ๋‹ค๊ฐ€ T ์™€ C ์˜ ๊ด€๊ณ„๊ฐ€ ๊ฐ€๋Šฅํ•œ์ง€์— ๋Œ€ํ•œ ์ถฉ๋ถ„ํ•œ ์ •๋ณด๋„ ์—†๋Š” ์ƒํ™ฉ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ๋ถˆํ™•์‹ค์„ฑ์„ ์ค„์ด๊ธฐ ์œ„ํ•ด ํ•จ์ˆ˜๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ€๊ฒฝํ•ด๋ณด์ž.

func makeProtocolContainer<T>(item: T) -> Container {
    [item]
}

Array ๋Š” Associated Types ๋ฅผ ์‚ฌ์šฉํ•ด ๋ฌด์—‡์ด๋“  ์ €์žฅํ•  ์ˆ˜ ์žˆ๊ณ , Array ๋Š” Container Protocol ์„ ์ค€์ˆ˜ํ•˜๋ฏ€๋กœ ์ด์ œ makeProtocolContainer(item:)์€ ์ž‘๋™์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

let emptyContainer = makeProtocolContainer(item: 10)
print(type(of: emptyContainer)) // Array<Int>
print(emptyContainer)           // [10]

๋ฐ˜๋ฉด, Array Type ์ž„์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  Container ๋กœ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— Subscript ๋Š” ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค.

print(emptyContainer[0])        // error: value of type 'any Container' has no subscripts

Container ๋Š” Subscript ๋ฅผ ์š”๊ตฌ์‚ฌํ•ญ์œผ๋กœ ๊ฐ–๊ณ  ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด Container ์— Subscript ์— ๋Œ€ํ•œ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ถ”๊ฐ€ํ•ด๋ณด์ž.

protocol Container {
    associatedtype Item
    subscript(i: Int) -> Item { get }
}
extension Array: Container { }

Array ๋Š” ๋ชจ๋“  Types ๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, Container ์—ญ์‹œ Array ๊ฐ€ ์ €์žฅํ•œ ๋ชจ๋“  Types ์— ๋Œ€ํ•ด Subscript ๊ฐ€ ์ž‘๋™ํ•˜๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด Associated Type ์„ ์ด์šฉํ•ด ์œ„์™€ ๊ฐ™์ด ์ ํ•ฉ์„ฑ์„ ์ค€์ˆ˜ํ•˜๋„๋ก ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

func makeProtocolContainer<T>(item: T) -> Container {   // error: Use of protocol 'Container' as a type must be written 'any Container'
    [item]
}

๊ทธ๋ฆฌ๊ณ  Swift compiler ๋Š” Replace 'Container' with 'any Container' ๋ผ๋ฉฐ ๊ฒฝ๊ณ ๋ฅผ ๋„์šด๋‹ค. Associated Types ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋Š” Protocols ๋Š” Return Types ๋กœ ์‚ฌ์šฉ๋  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ด๋Š” ์•ž์—์„œ ๋งž๋‹ฅ๋œจ๋ฆฐ

func makeProtocolContainer<T, C: Container>(item: T) -> C {
    [item]  // error: Cannot convert return expression of type '[T]' to return type 'C'
}

์™€ ์œ ์‚ฌํ•œ ์ผ€์ด์Šค๋ผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

7. Opaque Type Resolve The Problem That Protocol Has an Associated Types

Container Protocol ์€ ๋‹ค์‹œ ์ฒ˜์Œ ์ •์˜ํ•˜๋ ค๋˜๋Œ€๋กœ ๋ฐ”๊พธ๊ณ  Swift compiler ๊ฐ€ ์‹œํ‚ค๋Š”๋Œ€๋กœ ๋”ฐ๋ผ๊ฐ€๋ณด์ž.

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
extension Array: Container { }
func makeProtocolContainer<T>(item: T) -> any Container {
    [item]
}

let anyContainer = makeProtocolContainer(item: 11)
print(type(of: anyContainer))   // Array<Int>
print(anyContainer)             // [11]
print(anyContainer.count)       // 1

let eleven = anyContainer[0]
print(type(of: eleven))         // Int
print(eleven)                   // 11

์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•œ๋‹ค. ์œ„ ๊ฒฝ์šฐ๋Š” Array ๊ฐ€ ์‹ค์ œ๋กœ Any Types ์— ๋Œ€ํ•ด ๋™์ž‘ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ Any๋‚˜ AnyObject๋Š” ๋ช…ํ™•ํžˆ ํ•„์š”ํ•œ ์ƒํ™ฉ์ด ์•„๋‹ˆ๋ฉด ์•ฑ์˜ ์ฝ”๋“œ๋ฅผ Type-Safeํ•˜์ง€ ์•Š๊ฒŒ ๋งŒ๋“ค๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ์„ ์ง€์–‘ํ•ด์•ผํ•œ๋‹ค.


์ด๋Ÿฐ ์ƒํ™ฉ์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ Opaque Return Types๋‹ค!

์ด๋ฒˆ์—๋Š” ๋‹ค์‹œ makeProtocolContainer(item:) ํ•จ์ˆ˜๋ฅผ Opaque Types some Container๋ฅผ return ํ•˜๋„๋ก ๋ฐ”๊ฟ”๋ณด์ž.

func makeProtocolContainer<T>(item: T) -> some Container {
    [item]
}

let opaqueContainer = makeProtocolContainer(item: 12)
print(type(of: opaqueContainer))    // Array<Int>
print(opaqueContainer)              // [12]
print(opaqueContainer.count)        // 1

let twelve = opaqueContainer[0]
print(type(of: twelve))             // Int
print(twelve)                       // 12

Opaque Return Types๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Any ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  Associated Types ๋ฅผ ๊ฐ–๋Š” Protocol ์„ return ํ•  ๋•Œ์˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.




Reference

  1. โ€œOpaque Types.โ€ The Swift Programming Language Swift 5.7. accessed Feb. 27, 2023, Swift Docs Chapter 23 - Opaque Types.