Рефакторинг ленивого функционального кода в Swift 5 - PullRequest
0 голосов
/ 10 января 2020

Я хочу перестроить какой-нибудь ленивый функционал Свифт. Чтобы объяснить ситуацию, я сначала объясню эквивалентную нетерпеливую ситуацию:

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

Мы можем преобразовать карту и 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))

Yikes!

На первый взгляд выглядит довольно тревожно, но это правильно, потому что в отличие от нетерпеливого результата, который является массивом Ints, ленивый результат - итератор, который предоставит нам следующее число, когда мы его попросим, ​​и это должно знать, как вернуться через все вызовы функций обратно к исходной последовательности , Вот что описывает этот тип. Очень хорошо, что теперь у нас есть ключевое слово "some", как и в прошлом, если бы мы хотели указать явный тип, нам нужно было бы набрать все вышеперечисленное, что немного загадочно !!

Чтобы увидеть список чисел, который нам нужен, чтобы заставить их вычислять, что мы можем сделать, поместив ленивую последовательность в массив: print(Array(result))

И это дает точно такой же результат, как и раньше: [25, 26, 37 , 49, 50, 64, 65, 82, 100, 101]

Итак, теперь вызов.

Я хочу реорганизовать ленивый код так же, как Я сделал нетерпеливый код.

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 будет вероятно go прочь, если я починю остальных. Интересно, если Ошибка 2 означает, что попытка передать ленивую последовательность в squareAndInsertLazy вызывает рвение, и это означает, что [Int] представляется в squareAndInsertLazy. Я не могу понять, как двигаться вперед.

Любая помощь приветствуется.

1 Ответ

1 голос
/ 10 января 2020

Проблема здесь в том, что LazySequenceProtocol - это PAT (протокол с ассоциированным типом). Поэтому, когда вы вызываете squareAndInsertLazy(), он возвращает some LazySequenceProtocol и не знает, что это за элементы.

Вы можете увидеть, в чем проблема, закомментировав свой .filter { $0 % 3 != 0 } и заменив его на .filter { _ in true } , Он будет совершенно счастлив и не будет жаловаться, потому что ему все равно, какой тип элементов в последовательности.

Вы также можете увидеть это, используя:

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

Если вы затем Option нажмите на copy, и он покажет вам тип: (some LazySequenceProtocol).Element, который не может использоваться напрямую и должен быть определен компилятором. Вы не можете сделать let copy: (some LazySequenceProtool).Element = value он не скомпилируется.

Итак, теперь мы выяснили, в чем проблема, каковы ваши возможные решения?

1) Не возвращать some PAT в этом случае some LazySequenceProtocol и вернуть конкретный тип, который будет LazySequence<FlattenSequence<LazyMapSequence<LazyMapSequence<Self.Elements, Int>, [Int]>>>.

2) Go Вернуться к действующему.

3) Создать протокол, который реализует LazySequenceProtocol и уточняет Element до Int следующим образом:

protocol LazySequenceOfInt: LazySequenceProtocol where Element == Int {}
extension LazySequence: LazySequenceOfInt where Element == Int {}

Затем вы будете использовать some LazySequenceOfInt. Если вы сделаете это, вы, возможно, также захотите расширить другие типы Lazy, чтобы они соответствовали LazySequenceOfInt, чтобы их также можно было использовать. В данном конкретном случае LazySequence - единственный, который вам нужен.

...