【问题标题】:Refactoring lazy functional code in Swift 5在 Swift 5 中重构惰性函数代码
【发布时间】:2020-01-10 05:09:42
【问题描述】:

我想重构一些惰性函数 swift。 为了解释这种情况,我先解释一下等价的急切情况:

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)
}

我们可以将 map 和 flatMap 重构为一个单独的函数:

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]
}

所以现在我们将懒惰地重复这个过程。 第一个内联:

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)

}

哪些打印: 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 代码相同的方式重构惰性代码。

squareAndInsert 需要将LazySequenceProtocol<Int> 转换为some LazySequenceProtocol,所以我尝试了下面的代码,但得到了各种编译错误:

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)
}

如果我修复其他错误,我认为错误 1 ​​可能会消失。 我想知道错误 2 是否意味着试图将惰性序列传递给 squareAndInsertLazy 会迫使人们急于求成,这意味着 [Int] 被呈现给 squareAndInsertLazy。 我不知道如何前进。

任何帮助表示赞赏。

【问题讨论】:

    标签: swift functional-programming refactoring lazy-evaluation lazy-sequences


    【解决方案1】:

    这里的问题是LazySequenceProtocolPAT(具有关联类型的协议)。因此,当您调用 squareAndInsertLazy() 时,它会返回 some LazySequenceProtocol,并且它不再知道元素是什么。

    您可以通过注释掉您的 .filter { $0 % 3 != 0 } 并将其替换为 .filter { _ in true } 来查看问题所在。它会非常高兴并且不会抱怨,因为它不关心序列中元素的类型。

    您也可以使用:

    .filter { value in
        let copy = value
        return true
    }
    

    如果您然后选项单击copy,它将显示类型为:(some LazySequenceProtocol).Element,不能直接使用,必须由编译器推断。你不能这样做let copy: (some LazySequenceProtool).Element = value它不会编译。

    既然我们已经弄清楚了问题所在,您有什么可能的解决方案?

    1) 在这种情况下不要返回 some PAT some LazySequenceProtocol 并返回具体类型,即 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 是您唯一需要的。

    【讨论】:

    • 感谢您的解释@bscothern。我现在明白了。关于您的解决方案: 1)在我的应用程序中,我可能最终会得到大约 15 个级别的调用,并希望以不同的组合使用它们,因此具体类型会变得非常笨拙 - 因此想要重构和组合。 2)不会实现我需要的重构。 3) 看起来很有趣,但不能完全按照您的建议进行 - 您有时间解释更多或输入代码吗?谢谢
    • 是的,当我进入办公室时,我会为您提供更多示例和解释。
    • 啊哈——搞定了——正如你所说:定义 LazySequenceOfInt 如上所述,扩展 Lazy 序列如上,将“some LazySequenceProtocol”更改为“some LazySequenceOfInt”。
    • 我们的消息刚刚交叉 - 现在一切正常 - 感谢您的帮助。
    • 对于任何感兴趣的人,我已经进一步开发了一些东西并作为要点发布在:gist.github.com/adahus/69bbd8a79037832be22e9e4509bcc05f
    猜你喜欢
    • 2016-01-22
    • 2017-11-04
    • 2019-03-03
    • 1970-01-01
    • 2011-11-21
    • 1970-01-01
    • 2018-06-11
    • 1970-01-01
    • 2017-01-10
    相关资源
    最近更新 更多