I'm wanting to refactor some lazy functional swift. To explain the situation I'll explain the equivalent eager situation first:
let numbers = 1...10
do {
print("==================== EAGER INLINE =============================")
/// We start with a series of transformation on an array:
let result
= numbers
.filter { $0 >= 5 } /// Drop numbers less than 5
.map { $0 * $0 } /// Square the numbers
.flatMap { [$0, $0+1] } /// Insert number+1 after each number
.filter { $0 % 3 != 0 } /// Drop multiples of 3
print(result)
/// [25, 26, 37, 49, 50, 64, 65, 82, 100, 101]
/// which is [5^2, 5^2+1, 6^2+1, 7^2, 7^2+1, 8^2, 8^2+1, 9^2+1, 10^2, 10^2+1]
/// (Note 6^2 and 9^2 missing because they are divisible by 3)
}
We can refactor the map and the flatMap into a seperate function:
extension Array where Element == Int {
func squareAndInsert() -> [Int] {
self
.map { $0 * $0 }
.flatMap { [$0, $0+1] }
}
}
do {
print("==================== EAGER REFACTOR =============================")
let result
= numbers
.filter { $0 >= 5 }
.squareAndInsert()
.filter { $0 % 3 != 0 }
print(result)
/// Gives exactly the same result: [25, 26, 37, 49, 50, 64, 65, 82, 100, 101]
}
So now we'll repeat the process but lazily. First inline:
do {
print("==================== LAZY INLINE =============================")
let result: some LazySequenceProtocol /// ": some LazySequenceprotocol" not strictly
/// required but without it my compiler grumbled about complexity so this is to give the
/// compiler a nudge in the right direction.
= numbers
.lazy /// Note the ".lazy" added here to make the array lazy.
.filter { $0 >= 5 }
.map { $0 * $0 }
.flatMap { [$0, $0+1] }
.filter { $0 % 3 != 0 }
print(result)
}
Which prints:
LazyFilterSequence<FlattenSequence<LazyMapSequence<LazyMapSequence<LazyFilterSequence<ClosedRange<Int>>, Int>, Array<Int>>>>(_base: Swift.FlattenSequence<Swift.LazyMapSequence<Swift.LazyMapSequence<Swift.LazyFilterSequence<Swift.ClosedRange<Swift.Int>>, Swift.Int>, Swift.Array<Swift.Int>>>(_base: Swift.LazyMapSequence<Swift.LazyMapSequence<Swift.LazyFilterSequence<Swift.ClosedRange<Swift.Int>>, Swift.Int>, Swift.Array<Swift.Int>>(_base: Swift.LazyMapSequence<Swift.LazyFilterSequence<Swift.ClosedRange<Swift.Int>>, Swift.Int>(_base: Swift.LazyFilterSequence<Swift.ClosedRange<Swift.Int>>(_base: ClosedRange(1...10), _predicate: (Function)), _transform: (Function)), _transform: (Function))), _predicate: (Function))
Yikes!
Looks rather alarming at first sight but this is correct because unlike the eager result which is an array of Ints, the lazy result is an iterator which will provide us with the next number when we ask it to and this needs to know how to work back through all the function calls right back to the initial sequence. That's what this type is describing. Very nice now that we have the "some" keyword as in the past, if we wanted to put in an explicit type we would have to type all the above which is a bit of a mouthful !!
To see the list of numbers we need to force them to be calculated which we can do by putting the lazy sequence into an array: print(Array(result))
And this gives exactly the same result as before: [25, 26, 37, 49, 50, 64, 65, 82, 100, 101]
So now the challenge.
I want to refactor the lazy code in the same way that I did the eager code.
squareAndInsert needs to turn a LazySequenceProtocol<Int>
into some LazySequenceProtocol
so I try the code below but get various compile errors:
extension LazySequenceProtocol where Element == Int {
func squareAndInsertLazy() -> some LazySequenceProtocol {
self
.map { $0 * $0 }
.flatMap { [$0, $0+1] }
}
}
do {
print("==================== LAZY REFACTOR =============================")
let result: some LazySequenceProtocol // Error 1: Property declares an opaque return type, but cannot infer the underlying type from its initializer expression
= numbers
.lazy
.filter { $0 >= 5 }
.squareAndInsertLazy() // Error 2: Value of type '[Int]' has no member 'squareAndInsertLazy'
.filter { $0 % 3 != 0 } // Error 3: Protocol type 'Any' cannot conform to 'LazySequenceProtocol' because only concrete types can conform to protocols
// Error 4: Value of type 'Any' has no member 'filter'
print(result)
}
I think Error 1 would probably go away if I fix the others. I wonder if Error 2 means that trying to pass the lazy sequence into squareAndInsertLazy forces eagerness and this means [Int] is being presented to squareAndInsertLazy. I can't work out how to move forward.
Any help appreciated.