Введите стирание методом, использующим `Self` в качестве параметра

#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() . По этим двум причинам мы проверяем тип, а затем проверяем, перекрываются ли они.