在 Swift 5 中重构惰性函数代码
Refactoring lazy functional code in Swift 5
我想重构一些惰性函数 swift。
为了解释这种情况,我将首先解释等效的急切情况:
let numbers = 1...10
do {
print("==================== EAGER INLINE =============================")
/// We start with a series of transformation on an array:
let result
= numbers
.filter { [=11=] >= 5 } /// Drop numbers less than 5
.map { [=11=] * [=11=] } /// Square the numbers
.flatMap { [[=11=], [=11=]+1] } /// Insert number+1 after each number
.filter { [=11=] % 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)
}
我们可以将 map 和 flatMap 重构为一个单独的函数:
extension Array where Element == Int {
func squareAndInsert() -> [Int] {
self
.map { [=12=] * [=12=] }
.flatMap { [[=12=], [=12=]+1] }
}
}
do {
print("==================== EAGER REFACTOR =============================")
let result
= numbers
.filter { [=12=] >= 5 }
.squareAndInsert()
.filter { [=12=] % 3 != 0 }
print(result)
/// Gives exactly the same result: [25, 26, 37, 49, 50, 64, 65, 82, 100, 101]
}
所以现在我们将懒惰地重复该过程。
第一个内联:
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 { [=13=] >= 5 }
.map { [=13=] * [=13=] }
.flatMap { [[=13=], [=13=]+1] }
.filter { [=13=] % 3 != 0 }
print(result)
}
打印:
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))
赞!
第一眼看起来相当惊人,但这是正确的,因为与作为 Ints 数组的急切结果不同,惰性结果是一个迭代器,当我们要求它时,它会为我们提供下一个数字,这需要知道如何将所有函数调用返回到初始序列。这就是这种类型所描述的。非常好,现在我们像过去一样有了 "some" 关键字,如果我们想输入一个显式类型,我们将不得不输入上面的所有内容,这有点啰嗦!!
要查看数字列表,我们需要强制计算它们,我们可以通过将惰性序列放入数组中来实现:print(Array(result))
这给出了与之前完全相同的结果:[25, 26, 37, 49, 50, 64, 65, 82, 100, 101]
现在挑战。
我想像重构 eager 代码一样重构 lazy 代码。
squareAndInsert 需要将 LazySequenceProtocol<Int>
转换为 some LazySequenceProtocol
所以我尝试了下面的代码但出现了各种编译错误:
extension LazySequenceProtocol where Element == Int {
func squareAndInsertLazy() -> some LazySequenceProtocol {
self
.map { [=14=] * [=14=] }
.flatMap { [[=14=], [=14=]+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 { [=14=] >= 5 }
.squareAndInsertLazy() // Error 2: Value of type '[Int]' has no member 'squareAndInsertLazy'
.filter { [=14=] % 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)
}
我认为如果我修复其他错误 1 可能会消失。
我想知道错误 2 是否意味着尝试将惰性序列传递给 squareAndInsertLazy 会强制急切,这意味着 [Int] 被呈现给 squareAndInsertLazy。
我不知道如何前进。
感谢任何帮助。
这里的问题是 LazySequenceProtocol
是一个 PAT
(具有关联类型的协议)。所以当你调用 squareAndInsertLazy()
它 returns some LazySequenceProtocol
并且它不知道元素是什么了。
您可以通过注释掉您的 .filter { [=16=] % 3 != 0 }
并将其替换为 .filter { _ in true }
来查看问题所在。它会非常高兴并且不会抱怨,因为它不关心序列中元素的类型。
您还可以使用以下方式查看:
.filter { value in
let copy = value
return true
}
如果您然后单击 copy
选项,它会显示类型为:(some LazySequenceProtocol).Element
,不能直接使用,必须由编译器推断。你不能这样做 let copy: (some LazySequenceProtool).Element = value
它不会编译。
既然我们已经弄清楚了问题所在,您有什么可能的解决方案?
1) 在这种情况下,不要 return some PAT
some LazySequenceProtocol
和 return 具体类型 LazySequence<FlattenSequence<LazyMapSequence<LazyMapSequence<Self.Elements, Int>, [Int]>>>
.
2) 返回内联。
3) 创建一个实现 LazySequenceProtocol
的协议并将 Element
改进为 Int
,如下所示:
protocol LazySequenceOfInt: LazySequenceProtocol where Element == Int {}
extension LazySequence: LazySequenceOfInt where Element == Int {}
然后您将使用 some LazySequenceOfInt
。如果这样做,那么您可能还想扩展其他 Lazy
类型以符合 LazySequenceOfInt
以便它们也可以使用。在这种特殊情况下,LazySequence
是您唯一需要的。
我想重构一些惰性函数 swift。 为了解释这种情况,我将首先解释等效的急切情况:
let numbers = 1...10
do {
print("==================== EAGER INLINE =============================")
/// We start with a series of transformation on an array:
let result
= numbers
.filter { [=11=] >= 5 } /// Drop numbers less than 5
.map { [=11=] * [=11=] } /// Square the numbers
.flatMap { [[=11=], [=11=]+1] } /// Insert number+1 after each number
.filter { [=11=] % 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)
}
我们可以将 map 和 flatMap 重构为一个单独的函数:
extension Array where Element == Int {
func squareAndInsert() -> [Int] {
self
.map { [=12=] * [=12=] }
.flatMap { [[=12=], [=12=]+1] }
}
}
do {
print("==================== EAGER REFACTOR =============================")
let result
= numbers
.filter { [=12=] >= 5 }
.squareAndInsert()
.filter { [=12=] % 3 != 0 }
print(result)
/// Gives exactly the same result: [25, 26, 37, 49, 50, 64, 65, 82, 100, 101]
}
所以现在我们将懒惰地重复该过程。 第一个内联:
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 { [=13=] >= 5 }
.map { [=13=] * [=13=] }
.flatMap { [[=13=], [=13=]+1] }
.filter { [=13=] % 3 != 0 }
print(result)
}
打印:
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))
赞!
第一眼看起来相当惊人,但这是正确的,因为与作为 Ints 数组的急切结果不同,惰性结果是一个迭代器,当我们要求它时,它会为我们提供下一个数字,这需要知道如何将所有函数调用返回到初始序列。这就是这种类型所描述的。非常好,现在我们像过去一样有了 "some" 关键字,如果我们想输入一个显式类型,我们将不得不输入上面的所有内容,这有点啰嗦!!
要查看数字列表,我们需要强制计算它们,我们可以通过将惰性序列放入数组中来实现:print(Array(result))
这给出了与之前完全相同的结果:[25, 26, 37, 49, 50, 64, 65, 82, 100, 101]
现在挑战。
我想像重构 eager 代码一样重构 lazy 代码。
squareAndInsert 需要将 LazySequenceProtocol<Int>
转换为 some LazySequenceProtocol
所以我尝试了下面的代码但出现了各种编译错误:
extension LazySequenceProtocol where Element == Int {
func squareAndInsertLazy() -> some LazySequenceProtocol {
self
.map { [=14=] * [=14=] }
.flatMap { [[=14=], [=14=]+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 { [=14=] >= 5 }
.squareAndInsertLazy() // Error 2: Value of type '[Int]' has no member 'squareAndInsertLazy'
.filter { [=14=] % 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)
}
我认为如果我修复其他错误 1 可能会消失。 我想知道错误 2 是否意味着尝试将惰性序列传递给 squareAndInsertLazy 会强制急切,这意味着 [Int] 被呈现给 squareAndInsertLazy。 我不知道如何前进。
感谢任何帮助。
这里的问题是 LazySequenceProtocol
是一个 PAT
(具有关联类型的协议)。所以当你调用 squareAndInsertLazy()
它 returns some LazySequenceProtocol
并且它不知道元素是什么了。
您可以通过注释掉您的 .filter { [=16=] % 3 != 0 }
并将其替换为 .filter { _ in true }
来查看问题所在。它会非常高兴并且不会抱怨,因为它不关心序列中元素的类型。
您还可以使用以下方式查看:
.filter { value in
let copy = value
return true
}
如果您然后单击 copy
选项,它会显示类型为:(some LazySequenceProtocol).Element
,不能直接使用,必须由编译器推断。你不能这样做 let copy: (some LazySequenceProtool).Element = value
它不会编译。
既然我们已经弄清楚了问题所在,您有什么可能的解决方案?
1) 在这种情况下,不要 return some PAT
some LazySequenceProtocol
和 return 具体类型 LazySequence<FlattenSequence<LazyMapSequence<LazyMapSequence<Self.Elements, Int>, [Int]>>>
.
2) 返回内联。
3) 创建一个实现 LazySequenceProtocol
的协议并将 Element
改进为 Int
,如下所示:
protocol LazySequenceOfInt: LazySequenceProtocol where Element == Int {}
extension LazySequence: LazySequenceOfInt where Element == Int {}
然后您将使用 some LazySequenceOfInt
。如果这样做,那么您可能还想扩展其他 Lazy
类型以符合 LazySequenceOfInt
以便它们也可以使用。在这种特殊情况下,LazySequence
是您唯一需要的。