Как определить, является ли 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.
Ответы
Ответ 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
.