모듈 내부 타입만 채택할 수 있게 제한하고 싶지만 제네릭 제약으로는 쓰고 싶은 프로토콜이 있을 때, Swift에 프로토콜의 open 개념이 없는 것이 왜 아쉬운지에 대한 글.
URL: https://belkadan.com/blog/2021/11/Swift-Regret-Open-Protocols/?tag=swift-regrets
Title: Swift Regret: Open Protocols
Swift Regrets 시리즈의 일부.
가끔은 프로토콜이 “내 모듈 안의 타입들”이 채택하는 것만 의미가 있는데, 동시에 제네릭 제약으로는 사용하고 싶을 때가 있습니다. “PropertyList”가 좋은 예입니다. 다른 사람이 그냥 새로운 plist 타입을 만들어낼 수는 없으니까요.
먼저 말해두자면: 이 글은 Adrian M에 대한 감사 포스트입니다. 그들은 논란이 많았던 SE-0117 논의 당시 이 필요를 제기했는데, SE-0117은 클래스에 대해 public/open 구분을 처음 만든 제안이었습니다. 프로토콜은 이미 “커스터마이즈 지점”과 “고정된(extension의) 메서드”를 분리하고 있기 때문에, open의 주된(메서드 레벨의) 동기는 적용되지 않았습니다. 게다가 SE-0117은 이미 전장이었죠. 그래서 저와 다른 사람들은 Adrian에게 반대하며, 클래스에 집중해야 한다고 말했습니다.
하지만—대개 이런 일이 그렇듯이—그 이후로 저는 이것이 유용했을 곳들을 계속 보게 되었습니다. 라이브러리 작성자의 의도를 더 잘 전달한다든지, 의미 없는 기본 구현 없이도 새 요구사항(requirement)을 추가할 수 있게 한다든지 말이죠. 자주는 아니지만, 가끔은요.
이제, 닫힌(closed) 타입 집합에서 동작하는 도구가 하나 있긴 합니다. 바로 enum입니다. 하지만 enum은 프로토콜이 할 수 있는 모든 일을 하진 못합니다. 각 case마다 다른 associated type을 가질 수도 없고, 어떤 Set<T>든 담을 수 있는 case도 만들 수 없습니다.1 게다가 사용 편의를 위한 암시적 변환(implicit conversion)도 없습니다. 한편, non-open 프로토콜은 Scala와 Java(그리고 Kotlin)에도 선례가 있으니, 완전히 새로운 땅을 개척하는 것도 아닙니다.
이건 틈새(niche) 영역이고 문서화로도 커버할 수 있기 때문에 큰 후회는 아닙니다. 또한 언젠가는 public 프로토콜에 non-public 요구사항을 추가할 동기가 생길 거라고도 생각합니다. 그러면 효과는 같아집니다. (모듈 밖의 타입은 그 요구사항을 절대 만족할 수 없으니까요.) 하지만 설령 우리가 그걸 하게 되더라도, 클래스에서 가지고 있는 단순한 형태와는 여전히 다를 것이고, 라이브러리 작성자에게는 특별한 이유도 없이 잘못된 기본값이 될 겁니다.
…물론 -swift-version에서 기본값을 바꾼다면 가능하겠죠. 하지만 그건 요구가 너무 큽니다.
P.S. 테스트에 대해 걱정하는 분들을 위해: testable import는 open이 아닌 클래스도 서브클래싱을 허용한다는 걸 기억하세요. open이 아닌 프로토콜에도 똑같이 적용될 겁니다.
case items<T>(Set<T>) 같은 것을 지원하는 언어들이 있는데, 이는 Generalized Algebraic Datatypes, 즉 GADT라는 기능입니다. Swift에도 이를 추가할 수는 있겠지만, 그건 완전히 별개의 설계·제안·구현이 필요합니다.↩︎이 글은 2021년 11월 24일에 게시되었고, Technical 카테고리에 속합니다. 태그: Swift, Swift regrets