| :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``. |