泛型类型之间的循环依赖关系(CollectionType及其索引/生成器,例如)

时间:2015-07-25 20:50:14

标签: swift generics protocols persistent-storage

鉴于基于struct的通用CollectionType ...

struct MyCollection<Element>: CollectionType, MyProtocol {
    typealias Index = MyIndex<MyCollection>

    subscript(i: Index) -> Element { … }

    func generate() -> IndexingGenerator<MyCollection> {
        return IndexingGenerator(self)
    }
}

...如何为它定义Index ......

struct MyIndex<Collection: MyProtocol>: BidirectionalIndexType {

    func predecessor() -> MyIndex { … }
    func successor() -> MyIndex { … }
}

...没有引入死亡的依赖循环?

MyIndex的一般性质是必要的,因为:

  1. 它适用于任何类型的MyProtocol
  2. MyProtocol引用Self因此只能用作类型约束。
  3. 如果有前向声明(àObjective-C),我会 [sic!] MyIndex<MyCollection>添加一个MyCollection<…>indirect enum BinaryTree<Element>: CollectionType, BinaryTreeType { typealias Index = BinaryTreeIndex<BinaryTree> case Nil case Node(BinaryTree, Element, BinaryTree) subscript(i: Index) -> Element { … } } }。唉,没有这样的事情。

    可能的具体用例是二叉树,例如:

    Index

    这需要基于堆栈的struct BinaryTreeIndex<BinaryTree: BinaryTreeType>: BidirectionalIndexType { let stack: [BinaryTree] func predecessor() -> BinaryTreeIndex { … } func successor() -> BinaryTreeIndex { … } }

    struct

    人们不能(但是?)在Swift中将struct嵌套在通用BinaryTreeIndex<…>内。
    否则,我只需在BinaryTree<…>内移动BinaryTreeIndex

    此外,我更愿意使用一个通用BinaryTreeType
    然后可以使用任何类型的D3

2 个答案:

答案 0 :(得分:2)

您无法在结构中嵌套结构,因为它们是值类型。它们不是指向对象的指针,而是将它们的属性保存在变量中。想想结构是否包含自身,它的内存布局是什么样的?

前向声明在Objective-C中工作,因为它们随后用作指针。这就是indirect关键字被添加到枚举的原因 - 它告诉编译器通过指针添加间接级别。

理论上,相同的关键字可以添加到结构中,但它没有多大意义。你可以手工做indirect手工做的事情,但是有一个类框:

// turns any type T into a reference type
final class Box<T> {
    let unbox: T
    init(_ x: T) { unbox = x }
}

你可以用它来装箱一个结构来创建,例如一个链表:

struct ListNode<T> {
    var box: Box<(element: T, next: ListNode<T>)>?

    func cons(x: T) -> ListNode<T> {
        return ListNode(node: Box(element: x, next: self))
    }

    init() { box = nil }
    init(node: Box<(element: T, next: ListNode<T>)>?)
    { box = node }
}

let nodes = ListNode().cons(1).cons(2).cons(3)
nodes.box?.unbox.element // first element
nodes.box?.unbox.next.box?.unbox.element // second element

可以将此节点直接转换为集合,方法是将其与ForwardIndexTypeCollectionType相符,但这不是一个好主意。

例如,他们需要非常不同的==实现:

  • 索引需要知道来自同一列表的两个索引是否在同一位置。它不需要符合Equatable的元素。
  • 集合需要比较两个不同的集合,以查看它们是否包含相同的元素。它 需要元素符合Equatable,即:

    func == <T where T: Equatable>(lhs: List<T>, rhs: List<T>) -> Bool {
        // once the List conforms to at least SequenceType:
        return lhs.elementsEqual(rhs)
    }
    

最好将其包装在两种特定类型中。这是“免费的” - 包装器没有开销,只是帮助您更轻松地构建正确的行为:

struct ListIndex<T>: ForwardIndexType {
    let node: ListNode<T>

    func successor() -> ListIndex<T> {
        guard let next = node.box?.unbox.next
            else { fatalError("attempt to advance past end") }
        return ListIndex(node: next)
    }
}

func == <T>(lhs: ListIndex<T>, rhs: ListIndex<T>) -> Bool {
    switch (lhs.node.box, rhs.node.box) {
    case (nil,nil): return true
    case (_?,nil),(nil,_?): return false
    case let (x,y): return x === y
    }
}

struct List<T>: CollectionType {
    typealias Index = ListIndex<T>
    var startIndex: Index
    var endIndex: Index { return ListIndex(node: ListNode()) }
    subscript(idx: Index) -> T {
        guard let element = idx.node.box?.unbox.element
            else { fatalError("index out of bounds") }
        return element
    }
}

(无需实现generate() - 通过实施CollectionType,您可以在2.0中免费获得索引生成器

您现在拥有一个功能齐全的集合:

// in practice you would add methods to List such as
// conforming to ArrayLiteralConvertible or init from 
// another sequence
let list = List(startIndex: ListIndex(node: nodes))

list.first  // 3
for x in list { print(x) }  // prints 3 2 1

现在所有这些代码看起来都很恶心,原因有两个。

一个是因为box妨碍了,而indirect更好,因为编译器会为你彻底排除它。但它正在做类似的事情。

另一个是结构不是一个很好的解决方案。枚举好多了。实际上代码真的使用枚举 - 这就是Optional。仅代替nil(即Optional.None),最好在链表的末尾加上End个案例。这就是我们正在使用它。

有关此类内容的更多内容,您可以查看these posts

答案 1 :(得分:2)

虽然 Airspeed Velocity 的答案适用于最常见的情况,但我的问题是专门询问推广CollectionType索引的特殊情况,以便能够分享所有可想象的二叉树的单个Index实现(其递归性质使得必须使用堆栈进行基于索引的遍历(至少对于没有父指针的树)),这需要{{ 1}}专门针对实际的Index,而不是BinaryTree

我解决此问题的方法是将Element重命名为MyCollection,撤销其MyCollectionStorage符合性,并使用现在取代CollectionType的结构包装它处理符合MyCollection

让事情变得更加真实&#34;我将参考:

  • CollectionTypeMyCollection<E>
  • SortedSet<E>MyCollectionStorage<E>
  • BinaryTree<E>MyIndex<T>

所以不用多说:

BinaryTreeIndex<T>

这种依赖关系图从有向循环图转变为有向无环图