Back to Swift

Bridging Container Protocols to Class Clusters

docs/proposals/rejected/BridgingContainerProtocolsToClassClusters.rst

latest7.1 KB
Original Source

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