ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [WWDC2022] Embrace Swift Generic
    앱등이에게 살충제를 뿌린다./Apple Dev Reference 2022. 8. 8. 01:33

    https://developer.apple.com/videos/play/wwdc2022/110352/

     

    Embrace Swift generics - WWDC22 - Videos - Apple Developer

    Generics are a fundamental tool for writing abstract code in Swift. Learn how you can identify opportunities for abstraction as your code...

    developer.apple.com

     

     

    Swift의 추상화에 대한 이야기로 시작 - overload로 인한 보일러플레이트 발생, class사용 등

    Parametric polymorphism

    // 아래 세가지 선언은 완전히 동일하다.
    // As-is
    func feed<A: Animal>(animal: A)
    func feed<A>(animal: A) where A: Animal
    
    // To-be
    // This declaration is identical to the previous one(영상 13:04)
    func feed(animal: some Animal)
    • some Animal
      • some은 반드시 특정한 타입이 있다는 것을 의미(13:34). 그리고 그 타입은 반드시 Animal 프로토콜을 따른다는 것을 의미.
      • SwiftUI의 some View에서 사용하는 것과 동일함
    • An abstract type that represents a placeholder for a specific concrete type is called an opaque type. The specific concrete type that is substituted in is called an underlying type. For values with opaque type, the underlying type is fixed for the scope of the value. This way, generic code using the value is guaranteed to get the same underlying type each time the value is accessed.
    • some Animal, <T: Animal> 모두 opaque type을 선언하는 것이다.
    • Swift5.7부터 Parameter에 some을 사용할 수 있다.
    func feed(_ animal: some Animal) { 
    // 여기서 사용되는 opaque타입의 스코프는 메소드 내부이기 때문에, 서로 다른 Animal을 전달할 수 있다.
    // feed(Cow()), feed(Chicken())가 가능
    func makeView(for farm: Farm) -> some View {
    // 하지만 리턴타입에 opaque타입을 사용한다면? 스코프가 메소드/프로퍼티를 사용하는 모든 곳이된다.
    // 따라서 if someCondition { return AView() } else { return BView() } 같은 코드는 에러가 발생한다.
    // SwiftUI에서 이를 해결하기 위해 @ViewBuilder라는게 나온듯?

     

    struct Farm {
        func feedAll(_ animals: [???])
    }

    여기에 some Animal을 쓸 수 있을까?

    some을 사용하면 underlying type이 모두 동일하기 때문에 서로 다른 종의 동물로 배열을 만들 수 없다. 즉, some을 쓸 수 없다.

     

    A supertype that can represent any type of animal. We can express an arbitrary type of animal by writing any Aniaml.

    여기에서 any는 어떤 Animal타입도 가능하고 underlying type 또한 다양할 수 있음을 의미한다. (the underlying of animal can vary at runtime. 21:18)

     

    이렇게 유연한 저장(flexible storage)을 하기위해 any Animal타입은 메모리에 조금 특별한 형태로 저장된다.

     

    박스 하나를 메모리공간으로 보자. 작은 value는 박스에 충분히 들어갈 수 있지만 큰 value는 박스에 담을 수 없다. 그래서 다른 메모리공간에 큰 value를 올리고 박스는 그 메모리공간을 가리키게 된다. Existential Container 느낌도 좀 나는듯?

     

    여기서 any Animal을 Existential Type이라고도 함. (22:15) 아래 두 인스턴스의 실제 타입은 런타임까지 알 수 없다(Type erasure). 그래서 Chicken과 Cow는 동일한 Static Type을 갖지만 다른 Dynamic type을 갖는다.

     

    associated type을 갖는 Protocol에 any를 붙이는 문법은 Swift5.7부터 사용가능하다.

     

    protocol Animal {
        associatedtype Feed: AnimalFeed
        func eat(_ food: Feed)
    }
    
    struct Farm {
        func feed(_ animal: some Animal) {
            let crop = type(of: animal).Feed.grow()
            let produce = crop.harvest()
            animal.eat(produce)
        }
        
        func feedAll(_ animals: [any Animal]) {
            for animal in animals {
    	        // 구체적 타입(underlying type)을 알 수 없기 때문에 빌드에러
            	// error message: Member 'eat' cannot be used on value of type 'any Animal'
                animal.eat(<#T##food: Animal.Feed##Animal.Feed#>)
            }
        }
    }

    error message: Member 'eat' cannot be used on value of type 'any Animal'

    any Animal을 사용하여 구체적 타입관계를 모드 제거했다. (Eliminated tye type-level distinction between specific animal types)

    그래서 구체적 Animal타입에 의존하는 type relationship, associated type 모두 제거되었다. 그래서 이 코드를 빌드하는 시점에 animal이 어떤 타입의 Feed를 필요로하는지 알 수 없다.

     

    animal의 eat메소드를 바로 호출하기보다 some Animal을 전달받는 feed메소드를 다시보자.

    any Animal과 some Animal은 서로 다른 타입이다. 하지만 컴파일러는 underlying value를 언박싱하여 some Animal타입의 파라미터로 전달하여 any Animal의 인스턴스를 some Animal으로 전환할 수 있다. (Opening type-erased boxes)

     

    protocol Animal {
        associatedtype Feed: AnimalFeed
        func eat(_ food: Feed)
    }
    
    struct Farm {
        func feed(_ animal: some Animal) {
            let crop = type(of: animal).Feed.grow()
            let produce = crop.harvest()
            animal.eat(produce)
        }
        
        func feedAll(_ animals: [any Animal]) {
            for animal in animals {
    	        // 여기를 이렇게 수정해주면 컴파일러가 언박싱하여 underlying type에 접근가능
            	feed(animal)
            }
        }
    }

     

     

    some vs any

     

    Guide

    일단 some을 써라. 그리고 다양한 타입의 값을 저장해야하는 일이 생길 경우 some을 any로 바꿔라. 이렇게 하면 Type erasure, Semantic limitations만으로 Flexible Storage를 사용할 수 있다.

     

    댓글 0

Designed by Tistory.