Как определить, является ли 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 или другие случайные classы.

Так что, предположив, что это возможно, что за трюк?

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

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

 func realCount(x: [T?]) -> Int { return countElements(filter(x, { $0.getLogicValue() } ) ) } func realCount(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(x: S) -> S.IndexType.DistanceType { return countElements(x) } func countReal(x: S) -> Int { return countElements(filter(x, {$0.isReal()})) } 

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

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

TL; DR

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

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

Если вы просто хотите код, прокрутите вниз до «Решение» ниже.


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

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

 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 { ^ 

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

 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 , чтобы мы могли проверить, нет ли элемента или нет. Конечно, мы могли бы создать метод isNil() , но не добавление чего-либо к Необязательному было бы идеальным. К счастью, у него уже есть функция map которая может нам помочь.

Вот пример того, как flatMap функции map и flatMap :

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

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

 protocol OptionalType { associatedtype Wrapped @warn_unused_result func flatMap(@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(@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 .

Interesting Posts
Давайте будем гением компьютера.