Swift: Lazily encapsulating chains of map, filter, flatMap
Another approach to achieve what you want:
Edit: I tried:
var lazyCollection = self.lazy
for transform in transforms {
lazyCollection = lazyCollection.flatMap(transform) //Error
}
var iterator = lazyCollection.makeIterator()
You were very near to your goal, if the both types in the Error line was assignable, your code would have worked.
A little modification:
var lazySequence = AnySequence(self.lazy)
for transform in transforms {
lazySequence = AnySequence(lazySequence.flatMap(transform))
}
var iterator = lazySequence.makeIterator()
Or you can use reduce
here:
var transformedSequence = transforms.reduce(AnySequence(self.lazy)) {sequence, transform in
AnySequence(sequence.flatMap(transform))
}
var iterator = transformedSequence.makeIterator()
Whole code would be:
(EDIT Modified to include the suggestions from Martin R.)
let animals = ["bear", "dog", "cat"]
typealias Transform<Element> = (Element) -> [Element]
let containsA: Transform<String> = { $0.contains("a") ? [$0] : [] }
let plural: Transform<String> = { [$0 + "s"] }
let double: Transform<String> = { [$0, $0] }
extension Sequence {
func transform(_ transforms: [Transform<Element>]) -> AnySequence<Element> {
return transforms.reduce(AnySequence(self)) {sequence, transform in
AnySequence(sequence.lazy.flatMap(transform))
}
}
}
let transformed = animals.transform([containsA, plural, double])
print(Array(transformed))
Definition of flatMap in Swift
You were expecting
func flatMap<T>(_ transform: (Element) throws -> Array<T>) rethrows -> Array<T>
for Array
.
If you look closely, the above is merely a special case of the signature that you found, when SegmentOfResult == Array<T>
.
The flatMap
in Array
is defined not just for the case when SegmentOfResult == Array<T>
, but also for any SegmentOfResult
that is a Sequence
.
Why? Because we would like to define functions so that they work on as many types as possible (i.e. as general as possible). It turns out that flatMap
would also work if the function mapped to any Sequence
. That is, it doesn't depend on the fact that the function returns specifically an Array
. It only depends on the fact that the function returns some kind of a Sequence
. Check out how flatMap
is implemented here if you are interested.
For the same reason, flatMap
isn't just available on Array
, it's available on any type that conforms to Sequence
!
How to use a swift Instance method with flatMap?
With instance method, you cannot.
Instance method is a curry method, the type (String) -> () -> Array<String>
mean "a method take string and return a function which takes nothing and return array of string".
So you can do like this, but not as you write.
print(animals.flatMap{ String.double($0)() }) // ["Ant", "Ant", "Bear", "Bear", "Cat", "Cat"]
What you need is a static method. It simply take string and return array of string.
extension String {
static func double(_ string: String) -> [String] {
return [string, string]
}
}
print(animals.flatMap(String.double)) // ["Ant", "Ant", "Bear", "Bear", "Cat", "Cat"]
Swift writing map, length, filter as reduce
For a mapping which transforms a type T
into a (possibly different) type S
simply use two type placeholders:
func map<T, S>(array: [T], f: (T->S)) -> [S] {
return array.reduce([]) {
(var seed, value) in
seed.append(f(value))
return seed
}
}
Example:
let x = map([1, 2, 3], f: { String($0) })
print(x) // ["1", "2", "3"]
Whether this "is this the most elegant syntax" is also a
matter of personal opinion. If you replace the append()
method
by array concatenation with +
then the seed
parameters needs
not be variable:
func map<T, S>(array: [T], f: (T->S)) -> [S] {
return array.reduce([]) {
(seed, value) in
seed + [f(value)]
}
}
Which can be written with shorthand parameter names as
func map<T, S>(array: [T], f: (T->S)) -> [S] {
return array.reduce([]) { $0 + [ f($1) ] }
}
Similarly:
func filter<T>(array: [T], predicate: (T->Bool)) -> [T]{
return array.reduce([]) { predicate($1) ? $0 + [ $1 ] : $0 }
}
Just note that an implementation of map()
and filter()
usingreduce()
(instead of a loop) is quite ineffective because a new array is created in each reduction step. See for example
- Arrays, Linked Lists and Performance
for an analysis. For an exercise (as you said) it is fine, just
don't use something like this in production).
Using flatMap with dictionaries produces tuples?
I see that your question boils down to map
vs. flatMap
. map
is the more consistent one and it works as expected here so I won't delve into it.
Now on to flatMap
: your problem is one of the reasons that SE-0187 was proposed. flatMap
has 3 overloads:
Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element] where S : Sequence
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
When your
dictionary()
function returns a non-optional, it uses the first overload. Since a dictionary is a sequence of key-value tuples, an array of tuple is what you get.When your
dictionary()
function returns an optional, it uses the third overload, which essentially filters out thenil
s. The situation can be very confusing so SE-0187 was proposed (and accepted) to rename this overload tocompactMap
.
Starting from Swift 4.1 (Xcode 9.3, currently in beta), you can use compactMap
:
// Assuming dictionary() returns [String: Any]?
dictionary["objects"] = objects.compactMap { $0.dictionary() }
For Swift < 4.1, the solution you provided is the only one that works, because it gives the compiler a hint to go with the third overload:
dictionary["objects"] = objects.flatMap { $0.dictionary() } as [[String: Any]]
Related Topics
Swift: Nsstatusitem Menu Behaviour in 10.10 (E.G. Show Only on Right Mouse Click)
Nspopover to Start in a Detached State
Swiftui: How to Implement Edit Menu in MACos App
iOS 13 Modals - Calling Swipe Dismissal Programmatically
Not Getting Expected Delegate Calls When Trying to Restore In-App Purchases with Storekit
How to Use Socket in Swift (Connect, Send and Get Message)
How to Give PDF Data a Filename for User to Save in Swift
Create a New Window with Nswindow
Cllocation Distancefromlocation (In Swift)
A Codable Structure Contains a Protocol Property
Swift - Segue from Button Inside a Cell
How to Load the Photo Library into Uicollectionview? Swift
How to Know Where Optional Chaining Is Breaking
Best Way to Handle Errors from Async Closures in Swift 2
Using Structs (Bytes) with Swift - Struct to Nsdata and Nsdata to Struct