-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Item 31. 한정적 와일드카드를 사용해 API 유연성을 높이라 #87
Open
seizze
wants to merge
3
commits into
main
Choose a base branch
from
item31
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
# Item 31. 한정적 와일드카드를 사용해 API 유연성을 높이라 | ||
|
||
### 한정적 와일드카드 사용 | ||
|
||
```java | ||
public class Stack<E> { | ||
// ... | ||
public void pushAll(Iterable<E> src) { | ||
for (E e : src) | ||
push(e); | ||
} | ||
} | ||
``` | ||
|
||
이렇게 작성할 경우, 매개변수화 타입은 불공변이기 때문에 `Stack<Number>`와 같이 선언할 경우 `Number`의 하위 타입인 `Integer`도 입력할 수 없다. | ||
|
||
```java | ||
public void pushAll(Iterable<? extends E> src) { | ||
// ... | ||
} | ||
``` | ||
|
||
위와 같이 와일드카드 연산자를 사용하여 Iterable 인터페이스를 구현한 E의 하위 타입도 입력할 수 있게 만들 수 있다. | ||
|
||
### 스위프트 | ||
|
||
스위프트는 와일드카드 연산자가 없는데다가, 연관 타입을 가진 프로토콜은 Sequence<Int> 형태로도 쓸 수 없음 | ||
|
||
```swift | ||
public protocol Sequence { | ||
associatedtype Element ... | ||
// ... | ||
} | ||
|
||
func f(sequence: Sequence<Int>) {} // 불가능 | ||
func g<S: Sequence>() -> S where S.Element == Int { ... } // 복잡 | ||
``` | ||
|
||
아래 형태는 복잡하며, 내부 구현인 Element 등을 외부로 노출하고 있다. | ||
|
||
일반화하여 사용할 수 있도록 하려면 Type Erasing을 사용해야 한다. | ||
|
||
### Type Erasure | ||
|
||
조금 더 일반적인 타입을 만들기 위해 특정 타입을 지우는 것을 말함. | ||
|
||
내부 구현 타입을 래핑하여 외부로부터 숨긴다. | ||
|
||
구체적인 타입이 코드 베이스에 퍼지는 것을 막는 효과 | ||
|
||
코드는 draft... | ||
|
||
```swift | ||
// Sequence<Int> | ||
class MAnySequence<Element>: Sequence { | ||
class Iterator: IteratorProtocol { | ||
func next() -> Element? { | ||
fatalError("Must override next()") | ||
} | ||
} | ||
func makeIterator() -> Iterator { // type-erased public API | ||
fatalError("Must override makeIterator()") | ||
} | ||
static func make<S: Sequence>(_ seq: S) -> MAnySequence<Element> where S.Element == Element { | ||
return MAnySequenceImpl<S>(seq) | ||
} | ||
} | ||
|
||
private class MAnySequenceImpl<S: Sequence>: MAnySequence<S.Element> { | ||
class IteratorImpl: Iterator { | ||
var wrapped: S.Iterator | ||
|
||
init(_ wrapped: S.Iterator) { | ||
self.wrapped = wrapped | ||
} | ||
override func next() -> S.Element? { | ||
return wrapped.next() | ||
} | ||
} | ||
|
||
var sequence: S | ||
|
||
init(_ sequence: S) { | ||
self.sequence = sequence | ||
} | ||
override func makeIterator() -> IteratorImpl { | ||
return IteratorImpl(sequence.makeIterator()) | ||
} | ||
} | ||
|
||
MAnySequence.make([1, 2, 3, 4, 5]).forEach { print($0) } | ||
MAnySequence.make([1 ..< 4]).forEach { print($0) } | ||
``` | ||
|
||
연관 타입을 가진 프로토콜을 구체 타입으로 사용할 수 있도록 만들어준다. | ||
|
||
스위프트의 AnyCollection, AnyHashable 등의 erased type을 제공하고 있다. AnySequence 또한 Sequence를 래핑하여 타입을 모른 채로 iterate를 가능하도록 해 준다. | ||
|
||
### Type Erasure가 실제 사용되는 곳 | ||
|
||
```swift | ||
public protocol ObserverType { | ||
associatedtype Element | ||
// ... | ||
} | ||
|
||
// A type-erased ObserverType. | ||
public struct AnyObserver<Element> : ObserverType { | ||
// ... | ||
public init<Observer: ObserverType>(_ observer: Observer) where Observer.Element == Element { | ||
self.observer = observer.on | ||
} | ||
} | ||
``` | ||
|
||
```swift | ||
private func f1(_ arg: ObserverType<Int>) {} // Error | ||
private func f2(_ arg: AnyObserver<Int>) {} // Legal | ||
``` | ||
|
||
### 결론 | ||
|
||
자바에서 불공변인 것 때문에 하위 타입을 넣을 수 없는 문제점이 있으니, 한정적 와일드카드를 사용하여 하위 타입도 입력을 가능하게 만든다. | ||
|
||
스위프트 제네릭은 아직 제약이 많아서 와일드카드를 사용하지 못할 뿐더러 제네릭 프로토콜의 경우 내부 구현 타입까지 명시해야 함. 일반적인 타입으로 사용하도록 만들기 위한 타입 이레이징 기법이 존재한다. | ||
|
||
하지만 스위프트의 제네릭 또한 자바처럼 불공변이며, 와일드카드가 없기 때문에 하위 타입을 넣을 수 없다. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type Erasure 로 직접 타입을 랩핑하는 경우 대신 Opaque 타입을 통해 컴파일 타임에 에러를 안나게 하는 방법도 있더군요..
그래서 Opaque 타입을 가지고 이것저것 해봤는데
일반 메소드의 return 타입이나 properyt에는 사용할 수 있는데
매개변수나 clousure의 return 값으로는 사용할 수가 없네요.
혹시 Opapue type에 대해서는 어떻게 생각하시는지요~~~~?