blob: e427b4e327342154ac1a9c6fd81826183bb436e7 [file] [log] [blame]
: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``.