Подтвердить что ты не робот

Как определить, является ли generic дополнительным в Swift?

Я хочу расширить Array с помощью функции, которая возвращает количество всех элементов, отличных от nil, в массиве. В идеале это будет работать с массивом любых необязательных или необязательных типов. Я пробовал различные вещи, которые не удалось скомпилировать, разбил Xcode или и то, и другое. Я бы предположил, что это будет выглядеть примерно так:

extension Array {
    func realCount() -> Int {
        var cnt = 0
        for value in self {
            if value != nil {
                cnt++
            }
        }

        return cnt
    }
}

Здесь Свифт жалуется, что T не конвертируется в UInt8. Или иногда MirrorDisposition или другие случайные классы.

Итак, если предположить, что трюк?

Изменить: с Xcode 6 beta 5 это теперь компилируется, но не дает ожидаемых результатов. if value != nil каждый раз оценивает значение true.

4b9b3361

Ответ 1

Вы не можете сравнить произвольное значение с nil (EDIT: но см. комментарий Sulthan ниже: возможно, мы должны иметь возможность сравнивать произвольные значения с nil, остальная часть этого абзаца может быть истинна сегодня, но только из-за ошибки компилятора). Хотя Optional имеет некоторые биты синтаксического сахара, применяемые к нему, это действительно просто перечисление, а nil - это просто Optional.None. Вы хотите одно поведение для одного типа (Optional) и другое поведение для всех других типов. У Swift есть это через дженерики, просто не в расширениях. Вы должны включить его в функцию:

func realCount<T>(x: [T?]) -> Int {
  return countElements(filter(x, { $0.getLogicValue() } ) )
}

func realCount<T>(x: [T]) -> Int {
  return countElements(x)
}

let l = [1,2,3]
let lop:[Int?] = [1, nil, 2]

let countL = realCount(l) // 3
let countLop = realCount(lop) // 2

Этот подход гораздо более гибкий. Optional - это только один из многих типов, которые вы хотели бы использовать flatMap таким образом (например, вы могли бы использовать эту технику для обработки Результат).


EDIT: вы можете сделать это дальше, создав протокол для вещей, которые вы считаете "реальными". Таким образом, вам не нужно ограничивать это необязательными. Например:

protocol Realizable {
  func isReal() -> Bool
}

extension Optional: Realizable {
  func isReal() -> Bool { return self.getLogicValue() }
}

func countReal<S:Collection>(x: S) -> S.IndexType.DistanceType {
  return countElements(x)
}

func countReal<S:Collection where S.GeneratorType.Element:Realizable>(x: S) -> Int {
  return countElements(filter(x, {$0.isReal()}))
}

Это говорит, если я передаю коллекцию "реализуемых" вещей, а затем отфильтруйте их против своего правила. В противном случае просто посчитайте их. Хотя я, вероятно, не использовал бы эту функцию (это кажется очень особенным), концепция полезна. Более поздние абоненты могут добавлять новые "реализуемые" типы без изменения какого-либо вашего кода (или даже зная, как они реализованы). И это показывает, как иметь поведение по умолчанию для вещей, которые не реализуют ваш протокол.

Кстати, я использую Collections здесь только потому, что их легче подсчитать (и я немного небрежно отношусь к типам возвращаемых данных, заметим, что это DistanceType, а другой - Int). Получение типов на общих элементах, основанных на коллекции, по-прежнему выглядит довольно сложно (и часто сбой компилятора). Я подозреваю, что все это улучшится в следующих бета-версиях.

Ответ 2

TL; DR

Используя протокол, вы можете расширить SequenceType, чтобы подсчитать количество не-нил.

let array: [Int?] = [1, nil, 3]
assert(array.realCount == 2)

Если вам просто нужен код, прокрутите вниз до "Решение" ниже.


Мне нужно было сделать что-то подобное, чтобы создать способ array.removeNils() extension.

Проблема в том, что когда вы пытаетесь сделать что-то вроде:

extension SequenceType where Generator.Element == Optional { }

вы получаете:

error: reference to generic type 'Optional' requires arguments in <...>
extension SequenceType where Generator.Element == Optional {
                                                  ^
generic type 'Optional' declared here

Итак, вопрос в том, какой тип мы должны добавить внутри <>? Он не может быть жестко запрограммированным типом, так как мы хотим, чтобы он работал на что угодно, поэтому вместо этого мы хотим использовать общий тип T.

error: use of undeclared type 'T'
extension SequenceType where Generator.Element == Optional<T> {
                                                           ^

Похоже, нет способа сделать это. Однако с помощью протоколов вы действительно можете делать то, что хотите:

protocol OptionalType { }

extension Optional: OptionalType {}

extension SequenceType where Generator.Element: OptionalType {
  func realCount() -> Int {
    // ...
  }
}

Теперь он будет работать только с массивами с опциями:

([1, 2] as! [Int]).realCount() // syntax error: type 'Int' does not conform to protocol 'OptionalType'
([1, nil, 3] as! [Int?]).realCount()

Заключительная часть головоломки заключается в сравнении элементов с nil. Нам нужно расширить протокол OptionalType, чтобы мы могли проверить, есть ли элемент nil или нет. Конечно, мы могли бы создать метод isNil(), но не добавляли ничего в Необязательный, было бы идеально. К счастью, у него уже есть map функция, которая может нам помочь.

Вот пример того, как выглядят функции map и flatMap:

extension Optional {
  func map2<U>(@noescape f: (Wrapped) -> U) -> U? {
    if let s = self {
      return f(s)
    }
    return nil
  }

  func flatMap2<U>(@noescape f: (Wrapped) -> U?) -> U? {
    if let s = self {
      return f(s)
    }
    return nil
  }
}

Обратите внимание, что map2 (эквивалент функции map) возвращает f(s), если self != nil. Нам все равно, какое значение возвращается, поэтому мы можем сделать его возвратом true для ясности. Чтобы облегчить понимание функции, я добавляю явные типы для каждой из переменных:

protocol OptionalType {
  associatedtype Wrapped
  @warn_unused_result
  func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
}

extension Optional: OptionalType {}

extension SequenceType where Generator.Element: OptionalType {
  func realCount() -> Int {
    var count = 0
    for element: Generator.Element in self {
      let optionalElement: Bool? = element.map {
        (input: Self.Generator.Element.Wrapped) in
        return true
      }
      if optionalElement != nil {
        count += 1
      }
    }
    return count
  }
}

Чтобы уточнить, это то, к чему типичны типы типов:

  • НеобязательныйType.Wrapped == Int
  • SequenceType.Generator.Element == Необязательный
  • SequenceType.Generator.Element.Wrapped == Int
  • map.U == Bool

Конечно, realCount может быть реализован без всех этих явных типов, а с помощью $0 вместо true он не позволяет нам указывать _ in в функции map.


Решение

protocol OptionalType {
  associatedtype Wrapped
  @warn_unused_result
  func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
}

extension Optional: OptionalType {}

extension SequenceType where Generator.Element: OptionalType {
  func realCount() -> Int {
    return filter { $0.map { $0 } != nil }.count
  }
}

// usage:
assert(([1, nil, 3] as! [Int?]).realCount() == 2)

Главное отметить, что $0 является Generator.Element (т.е. OptionalType) и $0.map { $0 } преобразует его в Generator.Element.Wrapped? (например, Int?). Generator.Element или даже OptionalType нельзя сравнить с nil, но Generator.Element.Wrapped? можно сравнить с nil.