在 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 是您唯一需要的。