【问题标题】:Type erasure with method using `Self` as parameter使用“Self”作为参数的方法进行类型擦除
【发布时间】:2016-10-01 17:41:39
【问题描述】:

我正在尝试在 RangeClosedRange 周围进行类型擦除,但我被卡住了,因为它们有一些将 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
}

【问题讨论】:

  • 仍然没有答案?我越想,也许是错的。我的代码中也有类似的东西,它让我在试图提出解决方案时头疼......也许这是我们认为可能的事情,因为我们一生都在编写 OOD 代码,而这两件事就是不混合和匹配??????
  • 我也在某处读到过,如果你在协议中的函数或变量的结果中使用Self,它就不再是通用的了:/
  • 我找到的唯一答案是使用类。 chris.eidhof.nl/post/type-erasers-in-swift
  • @farzadshbfn 这里的问题有点不同,因为Self 类型要求没有定义不同类型的比较。有关详细信息,请参阅我的答案。

标签: swift range protocols type-erasure


【解决方案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&lt;RT: RangeType&gt;() 方法使用传递给初始化器的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>

这个结构实际上允许我们(间接地)通过AnyRangeoverlaps(_:) 方法和闭包在两个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&lt;R, Bound&gt;,它将编译,但如果每个比较的 range 属性的类型与从泛型初始化程序推断的类型 R 不同,它将崩溃。您还“不能显式特化泛型函数”,因此必须为 rangeComparison() 的结果指定类型。由于这两个原因,我们先检查类型,然后检查它们是否重叠。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-20
    • 1970-01-01
    • 1970-01-01
    • 2022-01-05
    相关资源
    最近更新 更多