File: Collections.md

package info (click to toggle)
swiftlang 6.2.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,856,264 kB
  • sloc: cpp: 9,995,718; ansic: 2,234,019; asm: 1,092,167; python: 313,940; objc: 82,726; f90: 80,126; lisp: 38,373; pascal: 25,580; sh: 20,378; ml: 5,058; perl: 4,751; makefile: 4,725; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (104 lines) | stat: -rw-r--r-- 5,014 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
# Collection Initializers

* Author(s): [Philippe Hausler](https://github.com/phausler)

[
[Source](https://github.com/apple/swift-async-algorithms/blob/main/Sources/AsyncAlgorithms/Dictionary.swift),
[Source](https://github.com/apple/swift-async-algorithms/blob/main/Sources/AsyncAlgorithms/RangeReplaceableCollection.swift),
[Source](https://github.com/apple/swift-async-algorithms/blob/main/Sources/AsyncAlgorithms/SetAlgebra.swift) |
[Tests](https://github.com/apple/swift-async-algorithms/blob/main/Tests/AsyncAlgorithmsTests/TestDictionary.swift),
[Tests](https://github.com/apple/swift-async-algorithms/blob/main/Tests/AsyncAlgorithmsTests/TestRangeReplaceableCollection.swift),
[Tests](https://github.com/apple/swift-async-algorithms/blob/main/Tests/AsyncAlgorithmsTests/TestSetAlgebra.swift),
]


## Introduction

`Array`, `Dictionary` and `Set` are some of the most commonly-used data structures for storing collections of elements. Having a way to transition from an `AsyncSequence` to a collection is not only a useful shorthand but a powerful way of expressing direct intent for how to consume an `AsyncSequence`.

This type of functionality can be useful for examples and testing, and also for interfacing with existing APIs that expect a fully-formed collection before processing it.

## Proposed Solution

Three categories of initializers will be added to provide initializers for those three primary types: `Array`, `Dictionary` and `Set`. However these initializers can be written in a generic fashion such that they can apply to all similar collections.

`RangeReplaceableCollection` will gain a new initializer that constructs a collection given an `AsyncSequence`. This will allow for creating arrays from asynchronous sequences but also allow for creating types like `Data` or `ContiguousArray`. Because of the nature of asynchronous sequences, this initializer must be asynchronous and declare that it rethrows errors from the base asynchronous sequence.

```swift
extension RangeReplaceableCollection {
  public init<Source: AsyncSequence>(
    _ source: Source
  ) async rethrows 
    where Source.Element == Element
}
```

`Dictionary` will gain a family of new asynchronous, rethrowing initializers to parallel the existing `Sequence`-based initializers. The initializers will be asynchronous to facilitate uniquing keys and other tasks that may be asynchronous, in addition to the asynchronous initialization of the dictionaries.

```swift
extension Dictionary {
  public init<S: AsyncSequence>(
    uniqueKeysWithValues keysAndValues: S
  ) async rethrows 
    where S.Element == (Key, Value)
    
  public init<S: AsyncSequence>(
    _ keysAndValues: S, 
    uniquingKeysWith combine: (Value, Value) async throws -> Value
  ) async rethrows
    where S.Element == (Key, Value)
    
  public init<S: AsyncSequence>(
    grouping values: S, 
    by keyForValue: (S.Element) async throws -> Key
  ) async rethrows
    where Value == [S.Element]
}
```

`SetAlgebra` will gain a new asynchronous, rethrowing initializer that constructs a `SetAlgebra` type given an `AsyncSequence`. This will allow for creating sets from asynchronous sequences and allow for creating types like `OptionSet` types or `IndexSet`.

```swift
extension SetAlgebra {
  public init<Source: AsyncSequence>(
    _ source: Source
  ) async rethrows
    where Source.Element == Element
}
```

## Detailed Design

Each of the initializers is intended for uses where the `AsyncSequence` being used for initialization is known to be finite. Common uses include: 

* Reading from files via the `AsyncBytes` style sequences or `lines` accessors.
* Gathering elements produced by a `TaskGroup`.
* Accessing a prefix of an indefinite `AsyncSequence`. 

Each of the initializers will use the `for`-`await`-`in`/`for`-`try`-`await`-`in` syntax to iterate the sequence directly in the initializer. In addition each initializer relies on the `AsyncSequence` being passed in to properly respect cancellation. In the cases where cancellation is a potential, developers should be ready to either check immediately or be ready for initialization based on a partial sequence, depending on the behavior of the `AsyncSequence` being used. 

### RangeReplaceableCollection

```swift
let contents = try await Data(URL(fileURLWithPath: "/tmp/example.bin").resourceBytes)
```

### Dictionary

```swift
let table = await Dictionary(uniqueKeysWithValues: zip(keys, values))
```

### SetAlgebra

```swift
let allItems = await Set(items.prefix(10))
```

## Alternatives Considered

The spelling of these initializers could be expressed as a trailing conversion. However, that can lead to hard-to-read chains of operations. Functionally these all belong to the `reduce` family of functions, but due to source readability concerns they are more ergonomic for understanding what the code does by using the initializer patterns.

## Credits/Inspiration

The direct inspiration for each initialization is from their standard library counterparts.