File: Inplace.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 (465 lines) | stat: -rw-r--r-- 14,238 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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
:orphan:

=====================
 In-Place Operations
=====================

:Author: Dave Abrahams
:Author: Joe Groff

:Abstract: The goal of efficiently processing complex data structures
  leads naturally to pairs of related operations such as ``+`` and
  ``+=``: one that produces a new value, and another that mutates on
  the data structure in-place.  By formalizing the relationship and
  adding syntactic affordances, we can make these pairs easier to work
  with and accelerate the evaluation of some common expressions.

Examples
========

In recent standard library design meetings about the proper API for
sets, it was decided that the canonical ``Set`` interface should be
written in terms of methods: [#operators]_ ::

  struct Set<Element> {
    public func contains(_ x: Element) -> Bool                // x ∈ A, A ∋ x
    public func isSubsetOf(_ b: Set<Element>) -> Bool         // A ⊆ B
    public func isStrictSubsetOf(_ b: Set<Element>) -> Bool   // A ⊂ B
    public func isSupersetOf(_ b: Set<Element>) -> Bool       // A ⊇ B
    public func isStrictSupersetOf(_ b: Set<Element>) -> Bool // A ⊃ B
    ...
  }

When we started to look at the specifics, however, we ran into a
familiar pattern::

  ...
    public func union(_ b: Set<Element>) -> Set<Element>        // A ∪ B
    public mutating func unionInPlace(_ b: Set<Element>)        // A ∪= B

    public func intersect(_ b: Set<Element>) -> Set<Element>    // A ∩ B
    public mutating func intersectInPlace(_ b: Set<Element>)    // A ∩= B

    public func subtract(_ b: Set<Element>) -> Set<Element>     // A - B
    public mutating func subtractInPlace(_ b: Set<Element>)     // A -= B

    public func exclusiveOr(_ b: Set<Element>) -> Set<Element>  // A ⊕ B
    public mutating func exclusiveOrInPlace(_ b: Set<Element>)  // A ⊕= B

We had seen the same pattern when considering the API for
``String``, but in that case, there are no obvious operator
spellings in all of Unicode.  For example::

  struct String {
    public func uppercase() -> String
    public mutating func uppercaseInPlace()

    public func lowercase() -> String
    public mutating func lowercaseInPlace()

    public func replace(
      _ pattern: String, with replacement: String) -> String
    public mutating func replaceInPlace(
      _ pattern: String, with replacement: String)

    public func trim() -> String
    public mutating func trimInPlace()
    ...
  }

It also comes up with generic algorithms such as ``sort()`` (which is
mutating) and ``sorted()``, the corresponding non-mutating version.


We see at least four problems with this kind of API:

1. The lack of a uniform naming convention is problematic.  People
   have already complained about the asymmetry between mutating
   ``sort()``, and non-mutating ``reverse()``.  The pattern used by
   ``sort()`` and ``sorted()`` doesn't apply everywhere, and penalizes
   the non-mutating form, which should be the more economical of the two.

2. Naming conventions that work everywhere and penalize the mutating
   form are awkward.  In the case of ``String`` it was considered bad
   enough that we didn't bother with the mutating versions of any
   operations other than concatenation (which we spelled using ``+``
   and ``+=``).

3. Producing a complete interface that defines both variants of each
   operation is needlessly tedious.  A working (if non-optimal)
   mutating version of ``op(x: T, y: U) -> T`` can always be defined
   as ::

     func opInPlace(x: inout T, y: U) {
       x = op(x, y)
     }

   Default implementations in protocols could do a lot to relieve
   tedium here, but cranking out the same ``xxxInPlace`` pattern for
   each ``xxx`` still amounts to a lot of boilerplate.

4. Without formalizing the relationship between the mutating and
   non-mutating functions, we lose optimization opportunities.  For
   example, it should be possible for the compiler to rewrite ::

     let x = a.intersect(b).intersect(c).intersect(d)

   as ::

     var t = a.intersect(b)
     t.intersectInPlace(c)
     t.intersectInPlace(d)
     let x = t

   for efficiency, without forcing the user to sacrifice expressivity.
   This optimization would generalize naturally to more common idioms
   such as::

     let newString = s1 + s2 + s3 + s4

   Given all the right conditions, it is true that a similar
   optimization can be made at runtime for COW data structures using a
   uniqueness check on the left-hand operand.  However, that approach
   only applies to COW data structures, and penalizes other cases.

The Proposal
============

Our proposal has four basic components:

1. Solve the naming convention problem by giving the mutating and
   non-mutating functions the same name.

2. Establish clarity at the point of use by extending the language to
   support a concise yet distinctive syntax for invoking the mutating
   operation.

3. Eliminate tedium by allowing mutating functions to be automatically
   generated from non-mutating ones, and, for value types, vice-versa
   (doing this for reference types is problematic due to the lack of a
   standard syntax for copying the referent).

4. Support optimization by placing semantic requirements on mutating
   and non-mutating versions of the same operation, and allowing the
   compiler to make substitutions.

Use One Simple Name
-------------------

There should be one simple name for both in-place and non-mutating
sorting: ``sort``.  Set union should be spelled ``union``.  This
unification bypasses the knotty problem of naming conventions and
makes code cleaner and more readable.

When these paired operations are free functions, we can easily
distinguish the mutating versions by the presence of the address-of
operator on the left-hand side::

  let z = union(x, y)  // non-mutating
  union(&x, y)         // mutating

Methods are a more interesting case, since on mutating methods,
``self`` is *implicitly* ``inout``::

  x.union(y) // mutating or non-mutating?

We propose to allow method pairs of the form:

.. parsed-literal::

  extension **X** {
    func *f*\ (p₀: T₀, p₁: T₁, p₂: T₂, ...p\ *n*: T\ *n*) -> **X**

    func **=**\ *f*\ (p₀: T₀, p₁: T₁, p₂: T₂, ...p\ *n*: T\ *n*) -> **Void**
  }

The second ``=f`` method is known as an **assignment method** [#getset]_.
Assignment methods are implicitly ``mutating``.
Together these two methods, ``f`` and ``=f``, are known as an
**assignment method pair**.  This concept generalizes in obvious ways
to pairs of generic methods, details open for discussion.

An assignment method is only accessible via a special syntax, for
example:

.. parsed-literal::

  x\ **.=**\ union(y)

The target of an assignment method is always required, even when the
target is ``self``::

  extension Set {
    mutating func frob(_ other: Set) {
      let brick = union(other) // self.union(other) implied
      self.=union(other)       // calls the assignment method
      union(other)             // warning: result ignored
    }
  }

Assignment Operator Pairs
-------------------------

Many operators have assignment forms, for instance, ``+`` has ``+=``, ``-``
has ``-=``, and so on. However, not all operators do; ``!=`` is not the
assignment form of ``!``, nor is ``<=`` the assignment form of ``<``. Operators
with assignment forms can declare this fact in their ``operator`` declaration:

.. parsed-literal::

  infix operator + {
    **has_assignment**
  }

For an operator *op* which ``has_assignment``, a pair of operator definitions
of the form:

.. parsed-literal::

  func *op*\ (**X**, Y) -> **X**

  func *op*\ =(**inout X**, Y) -> **Void**

is known as an **assignment operator pair**, and similar
generalization to pairs of generic operators is possible.

To avoid confusion, the existing ``assignment`` operator modifier, which
indicates that an operator receives one of its operands implicitly ``inout``,
shall be renamed ``mutating``, since it can also be applied to non-assignment
operators:

.. parsed-literal::

  postfix operator ++ {
    **mutating** // formerly "assignment"
  }

If an operator ``op`` which ``has_assignment`` is in scope, it is an error to
declare ``op=`` as an independent operator:

.. parsed-literal::

  operator *☃* { has_assignment }

  // Error: '☃=' is the assignment form of existing operator '☃'
  operator *☃=* { has_assignment }

Eliminating Tedious Boilerplate
===============================

Generating the In-Place Form
----------------------------

Given an ordinary method of a type ``X``:

.. parsed-literal::

  extension **X** {
    func *f*\ (p₀: T₀, p₁: T₁, p₂: T₂, ...p\ *n*: T\ *n*) -> **X**
  }

if there is no corresponding *assignment method* in ``X`` with the signature

.. parsed-literal::

  extension **X** {
    func *=f*\ (p₀: T₀, p₁: T₁, p₂: T₂, ...p\ *n*: T\ *n*) -> **Void**
  }

we can compile the statement

.. parsed-literal::

  x\ **.=**\ *f*\ (a₀, p₁: a₁, p₂: a₂, ...p\ *n*: a\ *n*)

as though it were written:

.. parsed-literal::

  x **= x.**\ *f*\ (a₀, p₁: a₁, p₂: a₂, ...p\ *n*: a\ *n*)

Generating the Non-Mutating Form
--------------------------------

Given an *assignment method* of a value type ``X``:

.. parsed-literal::

  extension **X** {
    func *=f*\ (p₀: T₀, p₁: T₁, p₂: T₂, ...p\ *n*: T\ *n*) -> **Void**
  }

if there is no method in ``X`` with the signature

.. parsed-literal::

  extension **X** {
    func *f*\ (p₀: T₀, p₁: T₁, p₂: T₂, ...p\ *n*: T\ *n*) -> **X**
  }

we can compile the expression

.. parsed-literal::

  **x.**\ *f*\ (a₀, p₁: a₁, p₂: a₂, ...p\ *n*: a\ *n*)

as though it were written:

.. parsed-literal::

  {
    (var y: X) -> X in
    y\ **.=**\ *f*\ (a₀, p₁: a₁, p₂: a₂, ...p\ *n*: a\ *n*)
    return y
  }(x)

Generating Operator Forms
-------------------------

If only one member of an *assignment operator pair* is defined, similar
rules allow the generation of code using the other member.  E.g.

we can compile

.. parsed-literal::

  x *op*\ **=** *expression*

as though it were written:

.. parsed-literal::

  x **=** x *op* (*expression*)

or

.. parsed-literal::

  x *op* *expression*

as though it were written:

.. parsed-literal::

  {
    (var y: X) -> X in
    y *op*\ **=**\ *expression*
    return y
  }(x)

Class Types
===========

Assignment and operators are generally applied to value types, but
it's reasonable to ask how to apply them to class types.  The first
and most obvious requirement, in our opinion, is that immutable class
types, which are fundamentally values, should work properly.

An assignment operator for an immutable class ``X`` always has the form:

.. parsed-literal::

  func *op*\ **=** (lhs: **inout** X, rhs: Y) {
    lhs = *expression creating a new X object*
  }

or, with COW optimization:

.. parsed-literal::

  func *op*\ **=** (lhs: **inout** X, rhs: Y) {
    if isUniquelyReferenced(&lhs) {
      lhs.\ *mutateInPlace*\ (rhs)
    }
    else {
      lhs = *expression creating a new X object*
    }
  }

Notice that compiling either form depends on an assignment to ``lhs``.

A method of a class, however, cannot assign to ``self``, so no
explicitly-written assignment method can work properly for an
immutable class. Therefore, at *least* until there is a way to reseat ``self``
in a method, explicitly-written assignment methods must be banned for
class types::

  // Invalid code:
  class Foo {
    let x: Int
    required init(x: Int) { self.x = x }

    func advanced(_ amount: Int) -> Self {
      return Self(x: self.x + amount)
    }

    // Error, because we can't reseat self in a class method
    func =advanced(amount: Int) {
      self = Self(x: self.x + amount)
      // This would also be inappropriate, since it would violate value
      // semantics:
      // self.x += amount
    }
  }

That said, given an explicitly-written
non-assignment method that produces a new instance, the rules given
above for implicitly-generated assignment method semantics work just
fine::

  // Valid code:
  class Foo {
    let x: Int
    required init(x: Int) { self.x = x }

    func advanced(_ amount: Int) -> Self {
      return Self(x: self.x + amount)
    }
  }

  var foo = Foo(x: 5)
  // Still OK; exactly the same as foo = foo.advanced(10)
  foo.=advanced(10)

The alternative would be to say that explicitly-written assignment methods
cannot work properly for immutable classes and "work" with reference
semantics on other classes.  We consider this approach indefensible,
especially when one considers that operators encourage writing
algorithms that can only work properly with value semantics and will
show up in protocols.

Assignment Methods and Operators In Protocols
=============================================

The presence of a ``=method`` signature in the protocol implies that
the corresponding non-assignment signature is available.  Declaring
``=method`` in a protocol generates two witness table
slots, one for each method of the implied pair.  If the
``=method`` signature is provided in the protocol, any
corresponding non-assignment ``method`` signature is ignored.  A type can
satisfy the protocol requirement by providing either or both members
of the pair; a thunk for the missing member of the pair is generated
as needed.

When only the non-assignment ``method`` member of a pair appears in the
protocol, it generates only one witness table slot.  The assignment
signature is implicitly available on existentials and archetypes, with
the usual implicitly-generated semantics.

----------

.. [#operators] Unicode operators, which dispatch to those methods,
   would also be supported.  For example, ::

     public func ⊃ <T>(a: Set<T>, b: Set<T>) -> Bool {
       return a.isStrictSupersetOf(b)
     }

   however we decided that these operators were sufficiently esoteric,
   and also inaccessible using current programming tools, that they
   had to remain a secondary interface.

.. [#getset] the similarity to getter/setter pairs is by no means lost on
          the authors.  However, omitting one form in this case has a
          very different meaning than in the case of getter/setter
          pairs.