Skip to content

Commit c582aa9

Browse files
authored
Rename chained(with:) to chain(_:_:) (#33)
* Rename `chained(with:)` to `chain(_:_:)` This follows a forum discussion about the `chained(with:)` name. The old names are preserved as deprecated symbols. * Remove conditional lazy conformance from `Chain2`
1 parent 53354f3 commit c582aa9

File tree

4 files changed

+100
-88
lines changed

4 files changed

+100
-88
lines changed

Guides/Chain.md

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55

66
Concatenates two collections with the same element type, one after another.
77

8-
This operation is available through the `chained(with:)` method on any sequence.
8+
This operation is available for any two sequences by calling the `chain(_:_:)`
9+
function.
910

1011
```swift
11-
let numbers = [10, 20, 30].chained(with: 1...5)
12+
let numbers = chain([10, 20, 30], 1...5)
1213
// Array(numbers) == [10, 20, 30, 1, 2, 3, 4, 5]
13-
//
14-
let letters = "abcde".chained(with: "FGHIJ")
14+
15+
let letters = chain("abcde", "FGHIJ")
1516
// String(letters) == "abcdeFGHIJ"
1617
```
1718

@@ -21,25 +22,29 @@ the shared conformances of the two underlying types.
2122

2223
## Detailed Design
2324

24-
The `chained(with:)` method is added as an extension method on the `Sequence`
25-
protocol:
25+
The `chain(_:_:)` function takes two sequences as arguments:
2626

2727
```swift
28-
extension Sequence {
29-
public func chained<S: Sequence>(with other: S) -> Concatenation<Self, S>
30-
where Element == S.Element
31-
}
28+
public func chain<S1, S2>(_ s1: S1, _ s2: S2) -> Chain2<S1, S2>
29+
where S1.Element == S2.Element
3230
```
3331

34-
The resulting `Chain` type is a sequence, with conditional conformance to
32+
The resulting `Chain2` type is a sequence, with conditional conformance to
3533
`Collection`, `BidirectionalCollection`, and `RandomAccessCollection` when both
36-
the first and second arguments conform. `Chain` also conforms to
37-
`LazySequenceProtocol` when the first argument conforms.
34+
the first and second arguments conform.
3835

3936
### Naming
4037

41-
This method’s and type’s name match the term of art used in other languages and
42-
libraries.
38+
This function's and type's name match the term of art used in other languages
39+
and libraries.
40+
41+
This operation was previously implemented as a `Sequence` method named
42+
`chained(with:)`, and was switched to a free function to align with APIs like
43+
`zip` and `product` after [a lengthy forum discussion][naming]. Alternative
44+
suggestions for method names include `appending(contentsOf:)`, `followed(by:)`,
45+
and `concatenated(to:)`.
46+
47+
[naming]: https://forums.swift.org/t/naming-of-chained-with/40999/
4348

4449
### Comparison with other languages
4550

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Read more about the package, and the intent behind it, in the [announcement on s
1818

1919
#### Combining collections
2020

21-
- [`chained(with:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Chain.md): Concatenates two collections with the same element type.
21+
- [`chain(_:_:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Chain.md): Concatenates two collections with the same element type.
2222
- [`product(_:_:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Product.md): Iterates over all the pairs of two collections; equivalent to nested `for`-`in` loops.
2323
- [`cycled()`, `cycled(times:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Cycle.md): Repeats the elements of a collection forever or a set number of times.
2424

Sources/Algorithms/Chain.swift

Lines changed: 47 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//===----------------------------------------------------------------------===//
1111

1212
/// A concatenation of two sequences with the same element type.
13-
public struct Chain<Base1: Sequence, Base2: Sequence>
13+
public struct Chain2<Base1: Sequence, Base2: Sequence>
1414
where Base1.Element == Base2.Element
1515
{
1616
/// The first sequence in this chain.
@@ -25,7 +25,7 @@ public struct Chain<Base1: Sequence, Base2: Sequence>
2525
}
2626
}
2727

28-
extension Chain: Sequence {
28+
extension Chain2: Sequence {
2929
/// The iterator for a `Chain` sequence.
3030
public struct Iterator: IteratorProtocol {
3131
@usableFromInline
@@ -35,7 +35,7 @@ extension Chain: Sequence {
3535
internal var iterator2: Base2.Iterator
3636

3737
@usableFromInline
38-
internal init(_ concatenation: Chain) {
38+
internal init(_ concatenation: Chain2) {
3939
iterator1 = concatenation.base1.makeIterator()
4040
iterator2 = concatenation.base2.makeIterator()
4141
}
@@ -52,7 +52,7 @@ extension Chain: Sequence {
5252
}
5353
}
5454

55-
extension Chain: Collection where Base1: Collection, Base2: Collection {
55+
extension Chain2: Collection where Base1: Collection, Base2: Collection {
5656
/// A position in a `Chain` collection.
5757
public struct Index: Comparable {
5858
// The internal index representation, which can either be an index of the
@@ -253,7 +253,7 @@ extension Chain: Collection where Base1: Collection, Base2: Collection {
253253
}
254254
}
255255

256-
extension Chain: BidirectionalCollection
256+
extension Chain2: BidirectionalCollection
257257
where Base1: BidirectionalCollection, Base2: BidirectionalCollection
258258
{
259259
@inlinable
@@ -270,45 +270,56 @@ extension Chain: BidirectionalCollection
270270
}
271271
}
272272

273-
extension Chain: RandomAccessCollection
273+
extension Chain2: RandomAccessCollection
274274
where Base1: RandomAccessCollection, Base2: RandomAccessCollection {}
275-
extension Chain: LazySequenceProtocol where Base1: LazySequenceProtocol {}
276275

277-
extension Chain: Equatable where Base1: Equatable, Base2: Equatable {}
278-
extension Chain: Hashable where Base1: Hashable, Base2: Hashable {}
276+
extension Chain2: Equatable where Base1: Equatable, Base2: Equatable {}
277+
extension Chain2: Hashable where Base1: Hashable, Base2: Hashable {}
279278

280279
//===----------------------------------------------------------------------===//
281-
// chained(with:)
280+
// chain(_:_:)
282281
//===----------------------------------------------------------------------===//
283282

283+
/// Returns a new sequence that iterates over the two given sequences, one
284+
/// followed by the other.
285+
///
286+
/// You can pass any two sequences or collections that have the same element
287+
/// type as this sequence. This example chains a closed range of `Int` with an
288+
/// array of `Int`:
289+
///
290+
/// let small = 1...3
291+
/// let big = [100, 200, 300]
292+
/// for num in chain(small, big) {
293+
/// print(num)
294+
/// }
295+
/// // 1
296+
/// // 2
297+
/// // 3
298+
/// // 100
299+
/// // 200
300+
/// // 300
301+
///
302+
/// - Parameters:
303+
/// - s1: The first sequence.
304+
/// - s2: The second sequence.
305+
/// - Returns: A sequence that iterates first over the elements of `s1`, and
306+
/// then over the elements of `s2`.
307+
///
308+
/// - Complexity: O(1)
309+
public func chain<S1, S2>(_ s1: S1, _ s2: S2) -> Chain2<S1, S2> {
310+
Chain2(base1: s1, base2: s2)
311+
}
312+
313+
// MARK: - Deprecations
314+
315+
@available(*, deprecated, renamed: "Chain2")
316+
public typealias Chain = Chain2
317+
284318
extension Sequence {
285-
/// Returns a new sequence that iterates over this sequence, followed by the
286-
/// given sequence.
287-
///
288-
/// You can pass a sequence or collection of any type that has the same
289-
/// element type as this sequence. This example chains a closed range of `Int`
290-
/// with an array of `Int`:
291-
///
292-
/// let small = 1...3
293-
/// let big = [100, 200, 300]
294-
/// for num in small.chained(with: big) {
295-
/// print(num)
296-
/// }
297-
/// // 1
298-
/// // 2
299-
/// // 3
300-
/// // 100
301-
/// // 200
302-
/// // 300
303-
///
304-
/// - Parameter other: The sequence to iterate over after this sequence.
305-
/// - Returns: A sequences that follows iteration of this sequence with
306-
/// `other`.
307-
///
308-
/// - Complexity: O(1)
309-
public func chained<S: Sequence>(with other: S) -> Chain<Self, S>
319+
@available(*, deprecated, message: "Use the chain(_:_:) function, instead.")
320+
public func chained<S: Sequence>(with other: S) -> Chain2<Self, S>
310321
where Element == S.Element
311322
{
312-
Chain(base1: self, base2: other)
323+
Chain2(base1: self, base2: other)
313324
}
314325
}

Tests/SwiftAlgorithmsTests/ChainTests.swift

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,78 +15,78 @@ import XCTest
1515
final class ChainTests: XCTestCase {
1616
// intentionally does not depend on `Chain.index(_:offsetBy:)` in order to
1717
// avoid making assumptions about the code being tested
18-
func index<A, B>(atOffset offset: Int, in chain: Chain<A, B>) -> Chain<A, B>.Index {
18+
func index<A, B>(atOffset offset: Int, in chain: Chain2<A, B>) -> Chain2<A, B>.Index {
1919
offset < chain.base1.count
2020
? .init(first: chain.base1.index(chain.base1.startIndex, offsetBy: offset))
2121
: .init(second: chain.base2.index(chain.base2.startIndex, offsetBy: offset - chain.base1.count))
2222
}
2323

2424
func testChainSequences() {
25-
let run = (1...).prefix(10).chained(with: 20...)
25+
let run = chain((1...).prefix(10), 20...)
2626
XCTAssertEqualSequences(run.prefix(20), Array(1...10) + (20..<30))
2727
}
2828

2929
func testChainForwardCollection() {
3030
let s1 = Set(0...10)
3131
let s2 = Set(20...30)
32-
let c = s1.chained(with: s2)
32+
let c = chain(s1, s2)
3333
XCTAssertEqualSequences(c, Array(s1) + Array(s2))
3434
}
3535

3636
func testChainBidirectionalCollection() {
3737
let s1 = "ABCDEFGHIJ"
3838
let s2 = "klmnopqrstuv"
39-
let c = s1.chained(with: s2)
39+
let c = chain(s1, s2)
4040

4141
XCTAssertEqualSequences(c, "ABCDEFGHIJklmnopqrstuv")
4242
XCTAssertEqualSequences(c.reversed(), "ABCDEFGHIJklmnopqrstuv".reversed())
43-
XCTAssertEqualSequences(s1.reversed().chained(with: s2), "JIHGFEDCBAklmnopqrstuv")
43+
XCTAssertEqualSequences(chain(s1.reversed(), s2), "JIHGFEDCBAklmnopqrstuv")
4444
}
4545

4646
func testChainIndexOffsetBy() {
4747
let s1 = "abcde"
4848
let s2 = "VWXYZ"
49-
let chain = s1.chained(with: s2)
49+
let c = chain(s1, s2)
5050

51-
for (startOffset, endOffset) in product(0...chain.count, 0...chain.count) {
52-
let start = index(atOffset: startOffset, in: chain)
53-
let end = index(atOffset: endOffset, in: chain)
51+
for (startOffset, endOffset) in product(0...c.count, 0...c.count) {
52+
let start = index(atOffset: startOffset, in: c)
53+
let end = index(atOffset: endOffset, in: c)
5454
let distance = endOffset - startOffset
55-
XCTAssertEqual(chain.index(start, offsetBy: distance), end)
55+
XCTAssertEqual(c.index(start, offsetBy: distance), end)
5656
}
5757
}
5858

5959
func testChainIndexOffsetByLimitedBy() {
6060
let s1 = "abcd"
6161
let s2 = "XYZ"
62-
let chain = s1.chained(with: s2)
62+
let c = chain(s1, s2)
6363

64-
for (startOffset, limitOffset) in product(0...chain.count, 0...chain.count) {
65-
let start = index(atOffset: startOffset, in: chain)
66-
let limit = index(atOffset: limitOffset, in: chain)
64+
for (startOffset, limitOffset) in product(0...c.count, 0...c.count) {
65+
let start = index(atOffset: startOffset, in: c)
66+
let limit = index(atOffset: limitOffset, in: c)
6767

6868
// verifies that the target index corresponding to each offset in `range`
6969
// can or cannot be reached from `start` using
70-
// `chain.index(start, offsetBy: _, limitedBy: limit)`, depending on the
70+
// `c.index(start, offsetBy: _, limitedBy: limit)`, depending on the
7171
// value of `beyondLimit`
7272
func checkTargetRange(_ range: ClosedRange<Int>, beyondLimit: Bool) {
7373
for targetOffset in range {
7474
let distance = targetOffset - startOffset
7575

7676
XCTAssertEqual(
77-
chain.index(start, offsetBy: distance, limitedBy: limit),
78-
beyondLimit ? nil : index(atOffset: targetOffset, in: chain))
77+
c.index(start, offsetBy: distance, limitedBy: limit),
78+
beyondLimit ? nil : index(atOffset: targetOffset, in: c))
7979
}
8080
}
8181

8282
// forward
8383
if limit >= start {
8484
// the limit has an effect
8585
checkTargetRange(startOffset...limitOffset, beyondLimit: false)
86-
checkTargetRange((limitOffset + 1)...(chain.count + 1), beyondLimit: true)
86+
checkTargetRange((limitOffset + 1)...(c.count + 1), beyondLimit: true)
8787
} else {
8888
// the limit has no effect
89-
checkTargetRange(startOffset...chain.count, beyondLimit: false)
89+
checkTargetRange(startOffset...c.count, beyondLimit: false)
9090
}
9191

9292
// backward
@@ -102,42 +102,38 @@ final class ChainTests: XCTestCase {
102102
}
103103

104104
func testChainIndexOffsetAcrossBoundary() {
105-
let chain = "abc".chained(with: "XYZ")
105+
let c = chain("abc", "XYZ")
106106

107107
do {
108-
let i = chain.index(chain.startIndex, offsetBy: 3, limitedBy: chain.startIndex)
108+
let i = c.index(c.startIndex, offsetBy: 3, limitedBy: c.startIndex)
109109
XCTAssertNil(i)
110110
}
111111

112112
do {
113-
let i = chain.index(chain.startIndex, offsetBy: 4)
114-
let j = chain.index(i, offsetBy: -2)
115-
XCTAssertEqual(chain[j], "c")
113+
let i = c.index(c.startIndex, offsetBy: 4)
114+
let j = c.index(i, offsetBy: -2)
115+
XCTAssertEqual(c[j], "c")
116116
}
117117

118118
do {
119-
let i = chain.index(chain.startIndex, offsetBy: 3)
120-
let j = chain.index(i, offsetBy: -1, limitedBy: i)
119+
let i = c.index(c.startIndex, offsetBy: 3)
120+
let j = c.index(i, offsetBy: -1, limitedBy: i)
121121
XCTAssertNil(j)
122122
}
123123
}
124124

125125
func testChainDistanceFromTo() {
126126
let s1 = "abcde"
127127
let s2 = "VWXYZ"
128-
let chain = s1.chained(with: s2)
128+
let c = chain(s1, s2)
129129

130-
XCTAssertEqual(chain.count, s1.count + s2.count)
130+
XCTAssertEqual(c.count, s1.count + s2.count)
131131

132-
for (startOffset, endOffset) in product(0...chain.count, 0...chain.count) {
133-
let start = index(atOffset: startOffset, in: chain)
134-
let end = index(atOffset: endOffset, in: chain)
132+
for (startOffset, endOffset) in product(0...c.count, 0...c.count) {
133+
let start = index(atOffset: startOffset, in: c)
134+
let end = index(atOffset: endOffset, in: c)
135135
let distance = endOffset - startOffset
136-
XCTAssertEqual(chain.distance(from: start, to: end), distance)
136+
XCTAssertEqual(c.distance(from: start, to: end), distance)
137137
}
138138
}
139-
140-
func testChainLazy() {
141-
XCTAssertLazy([1, 2, 3].lazy.chained(with: [4, 5, 6]))
142-
}
143139
}

0 commit comments

Comments
 (0)