compactMap on sequence() not lazy?
The issue is not compactMap
, per se. There are two issues:
If you want the sequence to call
compactMap
lazily, you need to uselazy
.It would appear that
first
is preventing the lazy behavior. If you usefirst(where:)
, though, you do enjoy the lazy behavior.
Thus, while somewhat inelegant, the following achieves what you’re looking for:
if let vc = (chain.lazy.compactMap { $0 as? ViewController }.first { _ in true } ) {
...
}
Or, as you say, you can implement first
(or lazyFirst
) on Sequence
:
extension Sequence {
var first: Element? {
return first { _ in true }
}
}
And then this more simplified rendition now is still lazy:
if let vc = chain.lazy.compactMap({ $0 as? ViewController }).first {
...
}
Swift 4.1 compactMap for lazy collections
Yes, absolutely the same. They just decided to remove the ambiguity of flatMap
functions and renamed one of them to compactMap
not to confuse developers.
Here is the proposal: Introduce Sequence.compactMap(_:)
We propose to deprecate the controversial version of a Sequence.flatMap method and provide the same functionality under a different, and potentially more descriptive, name.
What is the Swift compiler doing with my return type? Is it somehow casting?
No, there is no casting going on. There are simply two different flatMap
functions being called. LazyMapSequence
has two flatMap(_:)
functions (well, technically four, but two are deprecated).
In your first code block, this function is inferred (because this version of flatMap
has a return type that matches your allRegions
function's return type):
func flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence
And in your second code block, this function is inferred (because there is no type annotation on your local variable that's forcing it to choose the above version of flatMap
):
func flatMap<SegmentOfResult>(_ transform: @escaping (Element) -> SegmentOfResult) -> LazySequence<FlattenSequence<LazyMapSequence<LazyMapSequence<Base, Element>, SegmentOfResult>>> where SegmentOfResult : Sequence
How do I access a lazy sequence with an index?
You can access them in a similar fashion to how you access a string by an integer, i.e. using index(_:offsetBy:)
:
filtered[filtered.index(filtered.startIndex, offsetBy: 2)]
Accessing a lazy filter sequence is quite similar to accessing a string in that both are not O(1).
Alternatively, drop the first n items and then access the first item:
filtered.dropFirst(2).first
But I prefer the first one as "dropping" things just to access a collection, in my mind, is quite weird.
Why and when to use lazy with Array in Swift?
lazy
changes the way the array is processed. When lazy
is not used, filter
processes the entire array and stores the results into a new array. When lazy
is used, the values in the sequence or collection are produced on demand from the downstream functions. The values are not stored in an array; they are just produced when needed.
Consider this modified example in which I've used reduce
instead of count
so that we can print out what is happening:
Not using lazy
:
In this case, all items will be filtered first before anything is counted.
[1, 2, 3, -1, -2].filter({ print("filtered one"); return $0 > 0 })
.reduce(0) { (total, elem) -> Int in print("counted one"); return total + 1 }
filtered one
filtered one
filtered one
filtered one
filtered one
counted one
counted one
counted one
Using lazy
:
In this case, reduce
is asking for an item to count, and filter
will work until it finds one, then reduce
will ask for another and filter
will work until it finds another.
[1, 2, 3, -1, -2].lazy.filter({ print("filtered one"); return $0 > 0 })
.reduce(0) { (total, elem) -> Int in print("counted one"); return total + 1 }
filtered one
counted one
filtered one
counted one
filtered one
counted one
filtered one
filtered one
When to use lazy
:
option-clicking on lazy
gives this explanation:
From the Discussion for lazy
:
Use the lazy property when chaining operations:
to prevent intermediate operations from allocating storage
or
when you only need a part of the final collection to avoid unnecessary computation
I would add a third:
when you want the downstream processes to get started sooner and not have to wait for the upstream processes to do all of their work first
So, for example, you'd want to use lazy
before filter
if you were searching for the first positive Int
, because the search would stop as soon as you found one and it would save filter
from having to filter the whole array and it would save having to allocate space for the filtered array.
For the 3rd point, imagine you have a program that is displaying prime numbers in the range 1...10_000_000
using filter
on that range. You would rather show the primes as you found them than having to wait to compute them all before showing anything.
Sequence.compactMap equivalent for tuples with optional values
First, the last two compact maps can be combined:
compactMap { try? UIImage(data: Data(contentsOf: $0)) }
The word try
doesn't have to be immediately before the throwable call. It's just that any throwable call has to be in the scope of a try
expression.
Now since you want a tuple, you'd do:
compactMap { ($0, try? UIImage(data: Data(contentsOf: $0))) }
However, compactMap
expects a (URL, UIImage)?
, rather than a (URL, UIImage?)
. There is really no way to transform one to the other other than pattern matching. You can write an Optional
initialiser for this purpose:
extension Optional {
init<T, U>(_ tuple: (T?, U?)) where Wrapped == (T, U) {
switch tuple {
case (let t?, let u?):
self = (t, u)
default:
self = nil
}
}
}
let images = strings
.compactMap { URL(string: $0) }
.compactMap {
Optional(($0, try? UIImage(data: Data(contentsOf: $0))))
}
Alternatively, if you are being lazy, you can use map
on the try
expression, similar to what you did:
compactMap { url in
(try? UIImage(data: Data(contentsOf: url))).map {
(url, $0)
}
}
But I find this less readable.
Laziness in Swift
It avoids the creation of an intermediate array.
self.map(transform)
returns an array containing the results of the transformation of
all sequence elements, which would then be traversed to build the
resulting array with the non-nil elements.
lazy(self).map(transform)
is a sequence of the transformed elements, which is then
iterated over to get the non-nil elements. The transformed elements
are computed during the enumeration. (Each call to next()
on the lazy sequence produces one element by transforming the next
element of the original sequence.)
Both methods work. The lazy method would probably perform better
for large sequences, but that can depend on many factors (the size
of the array, whether the elements are value or reference types,
how costly it is to copy array elements etc). For small arrays
the lazy method would probably be slower due to the additional
overhead. In a concrete application, profiling with Instruments would
help to decide which method to use.
Related Topics
Swift Safely Unwrapping Optinal Strings and Ints
Synchronized Realm - Airplane Mode
How to Play Avplayeritems Immediately
Consuming a Soap Web Service with Swift
Self Refrence Inside Swift Closure Return Nil Some Time
How to Prevent Eventstore Access Error on First Run
Swift: Visual Glitches When Presenting a Main and Alternative (Login/Onboarding) Flow
Struggling with Notificationcenter/Combine in Swiftui/Avplayer
Swiftui Hierarchical Picker with Dynamic Data
How to Sort JSON Coming from Alamofire and Return Final JSON Object (Swiftyjson)
Swift Notification Fire from Datepicker
Create Generic Delegate for Class
Reflection with Swift - Get Functions Name of a Class
Remove Programmatically Added Uiimageview
How to Create a Multiline Textfield in Swiftui? Like the Notes App