Generics en Swift

Tal vez has escuchado hablar de Generics en Swift, si no es así no te preocupes en esta guía te explicare de forma sencilla en que consiste.

Como podrás ver la palabra Generic se define por si sola y si buscamos la definición seria "Que es general o se refiere a un conjunto de elementos del mismo género". Ahora preguntaras ¿Como funciona esto en Swift?, para esto veamos un sencillo ejemplo.

El Problema

Imagina que queremos implementar una simple calculadora, la cual tendría las funciones básicas como sumar, restar, dividir y multiplicar. Para este ejemplo vamos a implementar únicamente la función sumar.

class Calculator {  
    func sum(_ a: Int, plus b: Int) -> Int {
        return a + b
    }
}

Es una clase muy simple pero que nos ayudará a centrarnos en el objetivo de esta guía.

Si queremos usar la función sum(_:_:) seria así

let calculator = Calculator()

let a = 5 // Int  
let b = 10 // Int

calculator.sum(a, plus: b) // return 15  

Sin problema verdad? pero que pasa si queremos sumar dos números decimales.

let a = 5.2   // Double  
let b = 10.3  // Double

calculator.sum(a, plus: b) // Error  

El compilador nos arrojará el siguiente error Cannot convert value of type 'Double' to expected argument type 'Int', esto es debido a que la función sum(_:_:) espera como parámetros dos números enteros (Int) en lugar de decimales (Double). Podríamos solucionarlo de forma fácil pero poco elegante.

class Calculator {  
    func sum(_ a: Int, plus b: Int) -> Int {
        return a + b
    }

    // Bad Idea 🙈 please don't do that
    func sum(_ a: Double, plus b: Double) -> Double {
        return a + b
    }
...

Esto funciona pero nos obliga a repetir código lo cual no es buena idea y va en contra del principio DRY (Don't Repeat Yourself).

La Solución

Una función Generic nos permite definir parámetros de tipo genérico. Básicamente podemos asignar un placeholder o marcador que nos identifique el tipo de dato, para ello usamos los paréntesis angulares <> y el label que queramos <T>

Un poco de contexto, la sintaxis

Las funciones Generics se parecen mucho a las funciones convencionales, la diferencia radica en que usan un placeholder para identificar el tipo de dato y va encerrado entre paréntesis angulares <T>.

Los placeholders pueden incluir un Type Constraint que se indica por medio de el caracter : después de la definición del placeholder. Los Type Constraints pueden ser clases o protocolos. En el ejemplo a continuación podemos ver como se define una función genérica con dos placeholder cada uno con un Type Constraint diferente, una clase y un protocolo.

func myGenericFunction<C: SomeClass, P: SomeProtocol>(someC: C, someP: P) {  
    // some logic
}

Generics al rescate 😎

Ya con un poco mas de contexto retomemos nuestro problema inicial de la calculadora y miremos como seria una solución usando Generics.

protocol Numeric {  
   static func +(lhs: Self, rhs: Self) -> Self
}

extension Double : Numeric {}  
extension Int : Numeric {}

class Calculator {

    // Elegant way 🧐
    func sum<T: Numeric>(_ a: T, plus b: T) -> T {
        return a + b
    }
 }

Como veras la función sum(_:_:) cambio y notaras una sintaxis un poco extraña pero que ya habíamos visto.

Primero revisemos la función sum(_:_:), veras la sintaxis <T: Numeric> después del nombre. Se encuentra divido en dos partes la primera es la letra T (placeholder) esto simplemente es la notación para decirle a nuestra función que T será el alias que tendrá un posible tipo de dato.
Numeric esta después de los dos puntos, es un Type Constraint o restricción e indica que el tipo de dato que vamos a recibir deberá heredar de la clase o conformar el protocolo especificado, en este caso el protocolo Numeric.

Bien, ahora notaras que reemplazamos los tipos de datos específicos como Int o Double por T en cada parámetro incluyendo el return, lo que indica que la función sum(_:_:) debe recibir como parámetros dos valores del mismo tipo T.

Ahora si, ya podemos usar la función sum(_:_:) genérica con Int y Double como parámetros.

let calculator = Calculator()

let aDouble = 5.2  
let bDouble = 10.3

calculator.sum(aDouble, plus: bDouble) // return 15.5

let aInt = 3  
let bInt = 8

calculator.sum(aInt, plus: bInt)      // return 11  

Sencillo verdad?, ahora podrás ir descubriendo el potencial de esta característica que provee Swift.

Algunos casos donde podemos ver el uso son Array<Element> y Dictionary<key, value>.

Puede ver el código completo del ejemplo en GitHub

Bonus

Prorocolo Numeric
Este protocolo nos ayuda a especificar que quien lo conforme deberá implementar la función + y que tanto los parámetros como el tipo de retorno deberá ser del mismo tipo de dato de quien lo esta implementado.

Extensiones Int y Double
Las dos extensiones permiten indicar a los tipos de datos definidos en Swift en este caso Inty Double que conformen el protocolo Numeric para poder usar la función + dentro de la función suma(_:_:) por medio del tipo genérico T.

Jose Aponte

Desarrollador full-stack apasionado por las tecnologías de información y los lenguajes de programación. Me gustan divertirme con mi familia, mi lema es "Nunca paras de Aprender"

Bogota

Subscribe to Jappsku Engineering Blog

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!