1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
|
:orphan:
.. warning:: This proposal was rejected. We ultimately decided to keep Array as
a dual-representation struct.
Bridging Container Protocols to Class Clusters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I think that attempting to bridge ``NSArray`` to a concrete type like
``Array<T>`` will be a poor compromise, losing both the flexibility of NSArray's
polymorphism and the performance afforded by ``Array<T>``'s simplicity.
Here's what I propose instead:
- Rename our current ``Array`` back to ``Vector`` or perhaps something like
``ContiguousArray``.
- Redefine ``Array`` as a refinement of the ``Collection`` protocol
that has integer indices.
- Implement an ``ArrayOf<T>`` generic container, like ``AnyIterator`` and
``AnySequence``, that can hold an arbitrary type conforming to ``Array``.
- Bridge ``NSArray`` from ObjC to Swift ``ArrayOf<AnyObject>`` with value
semantics.
- Bridge ``Array``-conforming types with class element types in Swift to
ObjC as ``NSArray``.
Although I'll be talking about arrays in this proposal, I think the same
approach would work for ``NSDictionary`` and ``NSSet`` as well, mapping them
to generic containers for associative map and unordered container protocols
respectively.
NSArray vs Array
================
Despite their similar names, ``NSArray`` and Swift's ``Array`` have
fundamentally incompatible design goals. As the root of a class cluster,
``NSArray`` provides abstraction over many underlying data structures, trading
weaker algorithmic guarantees for better representational flexibility and
implementation encapsulation. Swift's ``Array``, on the other hand, is intended to be a direct
representation of a contiguous region of memory, more like a C array or C++'s
``vector``, minimizing abstraction in order to provide tight algorithmic
guarantees. Many ``NSArray`` implementations are lazy,
such as those over KVO properties or Core Data aggregates, and
transforming them to concrete ``Array``\ s would have unintended semantic
effects. And on the other side, the overhead of having to accommodate an
arbitrary ``NSArray`` implementation inside ``Array`` destroys ``Array``
as a simple, high-performance container. Attempting to bridge these two types
will result in an unattractive compromise to both sides, weakening the
algorithmic guarantees of Array while forgoing the full flexibility of
``NSArray``.
"Array" as a Refinement of the Collection Protocol
==================================================
Swift's answer to container polymorphism is its generics system. The
``Collection`` protocol provides a common interface to indexable containers
that can be used generically, which is exactly what ``NSArray`` provides in
Cocoa for integer-indexable container implementations. ``Array`` could be
described as a refinement of ``Collection`` with integer indices::
protocol Array : Collection {
where IndexType == Int
}
protocol MutableArray : MutableCollection {
where IndexType == Int
}
The familiar ``NSArray`` API can then be exposed using default implementations
in the ``Array`` protocol, or perhaps even on the more abstract ``Collection``
and ``Sequence`` protocols, and we can bridge ``NSArray`` in a way that plays
nicely with generic containers.
This naming scheme would of course require us to rename the concrete
``Array<T>`` container yet again. ``Vector`` is an obvious candidate, albeit
one with a C++-ish bent. Something more descriptive like ``ContiguousArray``
might feel more Cocoa-ish.
The ArrayOf<T> Type
===================
Although the language as implemented does not yet support protocol types for
protocols with associated types, DaveA devised a technique for implementing
types that provide the same effect in the library, such as his ``AnyIterator<T>``
and ``AnySequence<T>`` containers for arbitrary ``Stream`` and ``Sequence``
types. This technique can be extended to the ``Array`` protocol, using class
inheritance to hide the concrete implementing type behind an abstract base::
// Abstract base class that forwards the Array protocol
class ArrayOfImplBase<T> {
var startIndex: Int { fatal() }
var endIndex: Int { fatal() }
func __getitem__(_ i: Int) -> T { fatal() }
// For COW
func _clone() -> Self { fatal() }
}
// Concrete derived class containing a specific Array implementation
class ArrayOfImpl<T, ArrayT: Array where ArrayT.Element == T>
: ArrayOfImplBase<T>
{
var value: ArrayT
var startIndex: Int { return value.startIndex }
var endIndex: Int { return value.endIndex }
func __getitem__(_ i: Int) -> T { return __getitem__(i) }
// For COW
func _clone() -> Self { return self(value) }
}
// Wrapper type that uses the base class to erase the concrete type of
// an Array
struct ArrayOf<T> : Array {
var value: ArrayOfImplBase<T>
var startIndex: Int { return value.startIndex }
var endIndex: Int { return value.endIndex }
func __getitem__(_ i: Int) -> T { return value.__getitem__(i) }
init<ArrayT : Array where ArrayT.Element == T>(arr: ArrayT) {
value = ArrayOfImpl<T, ArrayT>(arr)
}
}
The mutable variant can use COW optimization to preserve value semantics::
struct MutableArrayOf<T> : MutableArray {
/* ...other forwarding methods... */
func __setitem__(_ i: Int, x: T) {
if !isUniquelyReferenced(value) {
value = value._clone()
}
value.__setitem__(i, x)
}
}
Bridging ``NSArray`` into Swift
===============================
We could simply make ``NSArray`` conform to ``Array``, which would be
sufficient to allow it to be stored in an ``ArrayOf<AnyObject>`` container.
However, a good experience for ``NSArray`` still requires special-case
behavior. In particular, ``NSArray`` in Cocoa is considered a value class,
and best practice dictates that it be defensively ``copy``-ed when used. In
Swift, we should give bridged NSArrays COW value semantics by default, like
``NSString``. One way to handle this is by adding a case to the ``ArrayOf``
implementation, allowing it to either contain a generic value or an ``NSArray``
with COW semantics.
Bridging Swift Containers to ``NSArray``
========================================
We could have an implicit conversion to ``NSArray`` from an arbitrary type
conforming to ``Array`` with a class element type, allowing ObjC APIs to work
naturally with generic Swift containers. Assuming we had support for
``conversion_to`` functions, it could look like this::
class NSArrayOf<ArrayT: Array where ArrayT.Element : class> : NSArray {
/* ...implement NSArray methods... */
}
extension NSArray {
@conversion_to
func __conversion_to<
ArrayT: Array where ArrayT.Element : class
>(arr: ArrayT) -> NSArray {
return NSArrayOf<ArrayT>(arr)
}
}
``NSArray`` has reference semantics in ObjC, which is a mismatch with
Swift's value semantics, but because ``NSArray`` is a value class, this is
probably not a problem in practice, because it will be ``copy``-ed as
necessary as a best practice. There also needs to be a special case for bridging
an ``ArrayOf<T>`` that contains an ``NSArray``; such a container should be
bridged directly back to the underlying unchanged ``NSArray``.
|