Skip to content

Commit 1afa93b

Browse files
author
Russ Bishop
committed
[stdlib] SR-361: Implement SE008: Add Lazy flatMap for Seq of Optionals
https://bugs.swift.org/browse/SR-361 Adds required LazyFilterRandomAccessCollection and the flatMap extensions.
1 parent e7c81c5 commit 1afa93b

File tree

3 files changed

+120
-5
lines changed

3 files changed

+120
-5
lines changed

stdlib/public/core/Filter.swift.gyb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ public func > <Base : Collection>(
165165
// collapsed into one `LazyFilterCollection` using conditional conformances.
166166
// Maybe even combined with `LazyFilterSequence`.
167167

168-
% for Traversal in ['Forward', 'Bidirectional']:
168+
% for Traversal in ['Forward', 'Bidirectional', 'RandomAccess']:
169169
% Self = "LazyFilter" + collectionForTraversal(Traversal)
170170
% Slice = sliceTypeName(traversal=Traversal, mutable=False, rangeReplaceable=False)
171171

@@ -244,7 +244,7 @@ public struct ${Self}<
244244
i = LazyFilterIndex(base: index)
245245
}
246246

247-
% if Traversal == 'Bidirectional':
247+
% if Traversal == 'Bidirectional' or Traversal == 'RandomAccess':
248248
@warn_unused_result
249249
public func index(before i: Index) -> Index {
250250
var i = i
@@ -315,7 +315,7 @@ extension LazySequenceProtocol {
315315
}
316316
}
317317

318-
% for Traversal in ['Forward', 'Bidirectional']:
318+
% for Traversal in ['Forward', 'Bidirectional', 'RandomAccess']:
319319

320320
extension LazyCollectionProtocol
321321
where

stdlib/public/core/FlatMap.swift

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,26 @@ extension LazySequenceProtocol {
2424
FlattenSequence<LazyMapSequence<Elements, SegmentOfResult>>> {
2525
return self.map(transform).flatten()
2626
}
27+
28+
/// Returns a `LazyMapSequence` containing the concatenated non-nil
29+
/// results of mapping transform over this `Sequence`. The elements of
30+
/// the result are computed lazily, each time they are read.
31+
///
32+
/// Use this method to receive only nonoptional values when your
33+
/// transformation produces an optional value.
34+
///
35+
/// - Parameter transform: A closure that accepts an element of this
36+
/// sequence as its argument and returns an optional value.
37+
@warn_unused_result
38+
public func flatMap<U>(
39+
_ transform: (Self.Elements.Iterator.Element) -> U?
40+
) -> LazyMapSequence<
41+
LazyFilterSequence<
42+
LazyMapSequence<Self.Elements, U?>>,
43+
U
44+
> {
45+
return self.map(transform).filter { $0 != nil }.map { $0! }
46+
}
2747
}
2848

2949
extension LazyCollectionProtocol {
@@ -42,6 +62,26 @@ extension LazyCollectionProtocol {
4262
> {
4363
return self.map(transform).flatten()
4464
}
65+
66+
/// Returns a `LazyMapCollection` containing the concatenated non-nil
67+
/// results of mapping transform over this collection. The elements of
68+
/// the result are computed lazily, each time they are read.
69+
///
70+
/// Use this method to receive only nonoptional values when your
71+
/// transformation produces an optional value.
72+
///
73+
/// - Parameter transform: A closure that accepts an element of this
74+
/// collection as its argument and returns an optional value.
75+
@warn_unused_result
76+
public func flatMap<U>(
77+
_ transform: (Self.Elements.Iterator.Element) -> U?
78+
) -> LazyMapCollection<
79+
LazyFilterCollection<
80+
LazyMapCollection<Self.Elements, U?>>,
81+
U
82+
> {
83+
return self.map(transform).filter { $0 != nil }.map { $0! }
84+
}
4585
}
4686

4787
extension LazyCollectionProtocol
@@ -67,4 +107,48 @@ extension LazyCollectionProtocol
67107
>> {
68108
return self.map(transform).flatten()
69109
}
110+
111+
/// Returns a `LazyMapBidirectionalCollection` containing the concatenated non-nil
112+
/// results of mapping transform over this collection. The elements of
113+
/// the result are computed lazily, each time they are read.
114+
///
115+
/// Use this method to receive only nonoptional values when your
116+
/// transformation produces an optional value.
117+
///
118+
/// - Parameter transform: A closure that accepts an element of this
119+
/// collection as its argument and returns an optional value.
120+
public func flatMap<U>(
121+
_ transform: (Self.Elements.Iterator.Element) -> U?
122+
) -> LazyMapBidirectionalCollection<
123+
LazyFilterBidirectionalCollection<
124+
LazyMapBidirectionalCollection<Self.Elements, U?>>,
125+
U
126+
> {
127+
return self.map(transform).filter { $0 != nil }.map { $0! }
128+
}
129+
}
130+
131+
extension LazyCollectionProtocol
132+
where
133+
Self : RandomAccessCollection,
134+
Elements : RandomAccessCollection
135+
{
136+
/// Returns a `LazyMapRandomAccessCollection` containing the concatenated non-nil
137+
/// results of mapping transform over this collection. The elements of
138+
/// the result are computed lazily, each time they are read.
139+
///
140+
/// Use this method to receive only nonoptional values when your
141+
/// transformation produces an optional value.
142+
///
143+
/// - Parameter transform: A closure that accepts an element of this
144+
/// collection as its argument and returns an optional value.
145+
public func flatMap<U>(
146+
_ transform: (Self.Elements.Iterator.Element) -> U?
147+
) -> LazyMapRandomAccessCollection<
148+
LazyFilterRandomAccessCollection<
149+
LazyMapRandomAccessCollection<Self.Elements, U?>>,
150+
U
151+
> {
152+
return self.map(transform).filter { $0 != nil }.map { $0! }
153+
}
70154
}

validation-test/stdlib/Lazy.swift.gyb

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,20 @@ tests.test("LazyFilterBidirectionalCollection/AssociatedTypes") {
11611161
indicesType: DefaultBidirectionalIndices<Subject>.self)
11621162
}
11631163

1164+
tests.test("LazyFilterRandomAccessCollection/AssociatedTypes") {
1165+
typealias Base = MinimalRandomAccessCollection<OpaqueValue<Int>>
1166+
typealias Subject = LazyFilterRandomAccessCollection<Base>
1167+
expectRandomAccessCollectionAssociatedTypes(
1168+
collectionType: Subject.self,
1169+
iteratorType: LazyFilterIterator<Base.Iterator>.self,
1170+
// FIXME(ABI): SubSequence should be `LazyFilterRandomAccessCollection<Base.Slice>`.
1171+
subSequenceType: RandomAccessSlice<Subject>.self,
1172+
indexType: LazyFilterIndex<Base>.self,
1173+
indexDistanceType: Base.IndexDistance.self,
1174+
indicesType: DefaultRandomAccessIndices<Subject>.self)
1175+
}
1176+
1177+
11641178
tests.test("lazy.filter/TypeInference") {
11651179
let baseArray: [OpaqueValue<Int>] = (0..<10).map(OpaqueValue.init)
11661180
do {
@@ -1190,7 +1204,7 @@ tests.test("lazy.filter/TypeInference") {
11901204
var filtered = MinimalRandomAccessCollection(elements: baseArray)
11911205
.lazy.filter { _ in true }
11921206
expectType(
1193-
LazyFilterBidirectionalCollection<
1207+
LazyFilterRandomAccessCollection<
11941208
MinimalRandomAccessCollection<OpaqueValue<Int>>
11951209
>.self,
11961210
&filtered)
@@ -1283,5 +1297,22 @@ do {
12831297
}
12841298
}
12851299

1286-
runAllTests()
1300+
% for Traversal in TRAVERSALS:
1301+
% TraversalCollection = collectionForTraversal(Traversal)
1302+
tests.test("${TraversalCollection}/lazy/flatMap") {
1303+
let sample = (0..<100)
1304+
let expected = sample.filter { $0 % 3 != 0 }.map { String($0) }
1305+
let base = Minimal${TraversalCollection}(elements: sample).lazy
1306+
1307+
var calls = 0
1308+
let mapped = sample.lazy.flatMap { (x) -> String? in
1309+
calls += 1
1310+
return x % 3 != 0 ? String(x) : nil
1311+
}
1312+
expectEqual(0, calls, "unexpected eagerness in \(base.dynamicType).flatMap")
1313+
1314+
checkSequence(expected, mapped, resiliencyChecks: .none)
1315+
}
1316+
%end
12871317

1318+
runAllTests()

0 commit comments

Comments
 (0)