#swift #range #protocols #type-erasure
#swift #диапазон #протоколы #тип-стирание
Вопрос:
Я пытаюсь выполнить стирание типа Range
, ClosedRange
но я застрял, потому что у них есть некоторые методы, которые принимают Self
в качестве параметра.
Все образцы стирания типов, найденные в Интернете, не имеют отношения к этому случаю.
Я пытаюсь сделать что-то невозможное?
Вот моя реализация (упрощенная) :
protocol RangeType {
associatedtype _Bound: Comparable
func overlaps(_ other: Self) -> Bool
}
struct AnyRange<Bound: Comparable>: RangeType {
typealias _Bound = Bound
private let _overlaps: (AnyRange<Bound>) -> Bool
init<R: RangeType>(_ range: R) where R._Bound == Bound {
// Cannot assign value of type (R) -> Bool to type (AnyRange<...>) -> Bool
self._overlaps = range.overlaps
}
func overlaps(_ other: AnyRange<Bound>) -> Bool {
return _overlaps(other)
}
}
extension Range: RangeType {
typealias _Bound = Bound
}
extension ClosedRange: RangeType {
typealias _Bound = Bound
}
Комментарии:
1. По-прежнему нет ответов на это? чем больше я думаю, возможно, это неправильно. У меня тоже есть что-то подобное в моем коде, и это доставляло мне головную боль, пытаясь найти решения… МОЖЕТ быть, это то, что мы считаем возможным, потому что мы всю жизнь кодировали OWD, и эти две вещи просто не сочетаются и не совпадают 😞
2. Я также где-то читал, что если вы используете
Self
в результате функции или переменной в протоколе, это больше не является общим :/3. Единственный ответ, который я нашел, использовал классы. chris.eidhof.nl/post/type-erasers-in-swift
4. @farzadshbfn Проблема здесь немного другая, потому что сравнение разных типов не определено требованиями
Self
к типу. Подробнее см. Мой ответ.
Ответ №1:
Прежде чем я предложу свое решение проблемы, сначала обратите внимание, что то, что вы пытаетесь сделать, может быть не определено. Протокол RangeType
гарантирует, что overlaps(_:)
это определено для экземпляров, тип которых совпадает с типом, реализующим функцию. Стирание типа, которое вы пытаетесь получить, имитируя стирание типа, AnyIterator
не может быть достигнуто таким образом, потому что, хотя AnyRange
может гарантировать, что границы идентичны, сами фактические базовые типы могут не быть (требование протокола).
Однако это решается здесь. Если вы хотите, вы можете добавить специальный случай для обработки сравнений между двумя разными типами (хотя false
, возможно, желательно выполнить оценку, как это делается здесь)
protocol RangeType {
associatedtype _Bound: Comparable
func overlaps(_ other: Self) -> Bool
}
struct RangeComparison<Range, Element>: Equatable where Range: RangeType, Range._Bound == Element {
static func ==(lhs: RangeComparison<Range, Element>, rhs: RangeComparison<Range, Element>) -> Bool { lhs.range.overlaps(rhs.range) }
var range: Range
}
struct AnyRange<Bound: Comparable>: RangeType {
// MARK: RangeType
typealias _Bound = Bound
// Calls the closure of the `_overlaps` property which shields each type and allows self to be passed through
func overlaps(_ other: AnyRange<Bound>) -> Bool { _overlaps.closure(other, self) }
// Shielding structure. Allows us to compare to `AnyRange` instances
private struct OverlapContainer<A, B> {
private(set) var closure: (A, B) -> Bool
init(closure: @escaping (A, B) -> Bool) { self.closure = closure }
}
private var _overlaps: OverlapContainer<AnyRange<Bound>, Self>
// Holds reference to the actual range type. Note that if this is a class type, a strong reference will be created
private let range: Any
/**
Represents this particular type. Should not be called elsewhere in the structure as the cast would fail if `RT != R`
passed to the initiliazer
NOTE: `RT` as the generic type is used instead of `R` to keep us aware of this fact
*/
private nonmutating func rangeComparison<RT: RangeType>() -> RangeComparison<RT, Bound> { RangeComparison<RT, Bound>(range: range as! RT) }
init<R: RangeType>(_ range: R) where R._Bound == Bound {
self.range = range
self._overlaps = .init { other, this in
let thisComparison: RangeComparison<R, Bound> = this.rangeComparison()
// If the two types are the same, the comparison can be made
if type(of: other.range).self == R.self {
let otherComparison: RangeComparison<R, Bound> = other.rangeComparison()
return thisComparison == otherComparison
}
else { print("Not the same type"); return false } // Otherwise the comparison is invalid
}
}
}
extension Range: RangeType {
typealias _Bound = Bound
}
extension ClosedRange: RangeType {
typealias _Bound = Bound
}
// Examples
let range: Range<Int> = .init(5...8)
let rangeII: ClosedRange<Int> = 1...6
let any: AnyRange<Int> = .init(range)
let anyII: AnyRange<Int> = .init(rangeII)
print(any.overlaps(anyII)) // false.` Range` is not the same type as `ClosedRange`
let rangeIII: ClosedRange<Double> = 3.0...5.5
let rangeIV: ClosedRange<Double> = 1.0...4.0
let anyIII: AnyRange<Double> = .init(rangeIII)
let anyIV: AnyRange<Double> = .init(rangeIV)
print(anyIII.overlaps(anyIV)) // true. Both are 'ClosedRange<Double>' and actually overlap one another
Здесь много чего, поэтому позвольте мне объяснить каждую часть
struct RangeComparison<Range, Element>: Equatable where Range: RangeType, Range._Bound == Element {
static func ==(lhs: RangeComparison<Range, Element>, rhs: RangeComparison<Range, Element>) -> Bool { lhs.range.overlaps(rhs.range) }
var range: Range
}
Эта структура используется для представления данного AnyRange
типа. Как я уже упоминал, сравнение любых двух RangeType
экземпляров не определено, если они не одного типа. Это обеспечивает среду для обеспечения того, чтобы это было так, а также позволяет удобно приравнивать два AnyRange
типа с помощью этой структуры.
rangeComparison<RT: RangeType>()
Метод использует тип RangeType
(R), переданный в инициализатор, и приводит range
свойство (заданное как Any
и присвоенное экземпляру, переданному инициализатору) к этому типу для создания RangeComparison
экземпляра. range
Свойство — это то, что сохраняет фактический базовый тип.
private struct OverlapContainer<A, B> {
private(set) var closure: (A, B) -> Bool
init(closure: @escaping (A, B) -> Bool) { self.closure = closure }
}
private var _overlaps: OverlapContainer<AnyRange<Bound>, Self>
Эта структура фактически позволяет нам (косвенно) сравнивать два AnyRange
экземпляра с помощью overlaps(_:)
метода AnyRange
и замыкания. Мы просто вызываем _overlaps
свойство closure
свойства, предоставляя другой AnyRange
экземпляр и копию этого экземпляра. Копия используется для обеспечения того, чтобы закрытие могло использоваться self
без необходимости использования self
, поскольку компилятор будет жаловаться, что «Экранирующее закрытие захватывает изменяющийся self
параметр» (отсюда и причина, которая OverlapContainer
имеет два общих типа).
init<R: RangeType>(_ range: R) where R._Bound == Bound {
self.range = range
self._overlaps = .init { other, this in
let thisComparison: RangeComparison<R, Bound> = this.rangeComparison()
// If the two types are the same, the comparison can be made
if type(of: other.range).self == R.self {
let otherComparison: RangeComparison<R, Bound> = other.rangeComparison()
return thisComparison == otherComparison
}
else { print("Not the same type"); return false } // Otherwise the comparison is invalid
}
}
Наконец, мы проверяем, имеют ли два сравнения один и тот же тип. Если вы попытаетесь указать каждый возвращаемый тип как RangeComparison<R, Bound>
, он будет компилироваться, но произойдет сбой, если типы каждого range
свойства сравнения не совпадают с типом R
, полученным из универсального инициализатора. Вы также «Не можете явно специализировать универсальную функцию» и поэтому должны указать тип для результата rangeComparison()
. По этим двум причинам мы проверяем тип, а затем проверяем, перекрываются ли они.