File: BridgingContainerProtocolsToClassClusters.rst

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (176 lines) | stat: -rw-r--r-- 7,266 bytes parent folder | download
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``.