type generic functions associated generics swift optional

functions - swift generics



¿Cómo determinar si un genérico es un opcional en Swift? (2)

Quiero extender una matriz con una función que devolvería un recuento de todos los elementos no nulos en una matriz. Idealmente, esto funcionaría con una matriz de cualquier tipo opcional o no opcional. Probé una variedad de cosas que no se compilaron, Xcode se estrelló o ambos. Habría asumido que se vería algo así:

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

Aquí Swift se queja de que T no es convertible a UInt8 . O a veces MirrorDisposition u otras clases aleatorias.

Asumiendo que es posible, ¿cuál es el truco?

Edición: a partir de Xcode 6 beta 5 esto ahora se compila pero no da los resultados esperados. if value != nil evalúa true cada vez.


No puede comparar un valor arbitrario con nil (EDITAR: pero vea el comentario de Sulthan a continuación; puede ser que debamos poder comparar valores arbitrarios con nil ; el resto de este párrafo puede ser cierto hoy, pero solo debido a un compilador insecto). Si bien Optional tiene algunos bits de azúcar sintáctica aplicados, en realidad es solo una enumeración, y nil es solo Optional.None . Optional.None . Desea un comportamiento para un tipo ( Optional ) y otro comportamiento para todos los demás tipos. Swift tiene eso a través de genéricos, pero no en extensiones. Tienes que convertirlo en una función:

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

Este enfoque es mucho más flexible. Optional es solo uno de los muchos tipos que querría hacer flatMap de esta manera (por ejemplo, podría usar esta misma técnica para manejar el Result ).

EDITAR: puede llevar esto más lejos creando un protocolo para las cosas que considera "reales". De esa manera no tienes que limitar esto a los Opcionales. Por ejemplo:

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

Esto dice, si paso una colección de cosas "realizables", luego las filtraré contra su regla. De lo contrario, solo cuéntalos. Si bien probablemente no usaría esta función (parece un caso muy especial), el concepto es útil. Las personas que llaman más tarde pueden agregar nuevos tipos "realizables" sin modificar ningún código (o incluso saber cómo se implementan). Y esto muestra cómo tener un comportamiento predeterminado para cosas que no implementan su protocolo.

Por cierto, estoy usando Colecciones aquí porque son más fáciles de contar (y estoy siendo un poco descuidado con respecto a los tipos de devolución; note que uno es el Tipo de Distancia y el otro es un Int). Obtener los tipos correctos en las funciones genéricas basadas en la Colección sigue siendo un poco complicado (ya menudo falla el compilador). Sospecho que todo esto mejorará en las próximas betas.


TL; DR

Al usar un protocolo, puede extender SequenceType para contar el número de no-niles.

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

Si solo desea el código, desplácese hacia abajo hasta "Solución" a continuación.

Necesitaba hacer algo similar para crear un método de extensión array.removeNils() .

El problema es que cuando intentas hacer algo como:

extension SequenceType where Generator.Element == Optional { }

usted obtiene:

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

Entonces, la pregunta es, ¿qué tipo debemos agregar dentro de <> ? No puede ser un tipo codificado, ya que queremos que funcione para cualquier cosa, por lo tanto, queremos un genérico como T

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

Parece que no hay manera de hacer esto. Sin embargo, con la ayuda de los protocolos, puedes hacer lo que quieras:

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

Ahora solo funcionará en matrices con opciones:

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

La última pieza del rompecabezas es comparar los elementos a nil . Necesitamos extender el protocolo OptionalType para permitirnos verificar si un artículo es nil o no. Claro que podríamos crear un método isNil() , pero no agregar nada a Opcional sería ideal. Afortunadamente, ya tiene una función de map que puede ayudarnos.

Aquí hay un ejemplo de cómo se flatMap funciones map y 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 } }

Observe que map2 (un equivalente de la función de map ) solo devuelve f(s) si self != nil . No nos importa realmente qué valor devuelve, así que podemos hacerlo true para mayor claridad. Para hacer que la función sea más fácil de entender, estoy agregando tipos explícitos para cada una de las variables:

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

Para aclarar, esto es lo que los tipos genéricos se asignan a:

  • OptionalType.Wrapped == Int
  • SequenceType.Generator.Element == Opcional
  • SequenceType.Generator.Element.Wrapped == Int
  • map.U == Bool

Por supuesto, realCount se puede implementar sin todos esos tipos explícitos, y al usar $0 lugar de true , evita que tengamos que especificar _ in en la función de map .

Solución

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)

La clave a tener en cuenta es que $0 es un Generator.Element (ie OptionalType ) y $0.map { $0 } convierte en un Generator.Element.Wrapped? (por ejemplo, Int?). Generator.Element o incluso OptionalType no se pueden comparar con nil , pero Generator.Element.Wrapped? Se puede comparar a nil .