Swift

Profundizando en las funciones de Swift

Descubre todo el potencial de las funciones en Swift

La semana pasada hablamos de la nueva característica de la versión 5.2 de Swift que permite llamar a la instancia de un tipo como si fuera una función. Las funciones en Swift tienen un enorme potencial para ayudarnos a resolver problemas de programación. Vamos a revisar algunas características interesantes y cómo pueden interactuar con otras funcionalidades del lenguaje.

Variables como funciones

Una de las características de Swift que puede llamar la atención es que las funciones pueden almacenarse en variables. No estamos hablando de los closures si no de las funciones estándar de Swift que se declaran con la sentencia func.

Vamos a declarar una función simple que reciba un nombre de tipo String y que devuelva otro String saludando a ese nombre.

func saluda(a nombre: String) -> String {
    "Hola \(nombre)"
}

Puedo llamar a la función y almacenar el resultado o bien almacenar la propia función. Para esta segunda opción debemos omitir los parámetros e indicar únicamente el nombre de la función.

// Almaceno el resultado de la función
let saludo = saluda(a: "Arturo Rivas")

// Elimino el resultado de la función
let funciónSaludo = saluda

El tipo de la constante funciónSaludo es (String) -> String por lo tanto, si lo hubiésemos definido con var como una variable, sólo podríamos asignarle funciones que recibiesen un parámetro String y devolviesen un valor también de tipo String.

Las funciones en Swift, no solo son funciones como tal, son tipos de datos que se definen con la unión de los tipos de sus parámetros de entrada y los de salida.

¿Tipos por valor o referencia?

Otra buena pregunta podría ser, ¿las funciones se pasan por referencia o por valor? Podemos verlo a continuación.

var textoDeSaludo = "Hola"

func saluda(a nombre: String) -> String {
    "\(textoDeSaludo) \(nombre)"
}

funciónSaludo("Arturo Rivas")
// Devuelve "Hola Arturo Rivas"

textoDeSaludo = "Hello"

funciónSaludo("Arturo Rivas")
// Devuelve "Hello Arturo Rivas"

Aquí estamos cometiendo un error de base al trabajar con funciones. ¿Por qué? Porque para que una función tenga su propia entidad, a mismos parámetros de entrada siempre ha de devolver el mismo resultado. Pero en este caso, al usar textoDeSaludo, que es una variable fuera del ámbito de la función que la usa, estamos viendo que cambia su resultado.

Podríamos asegurar que las funciones se almacenan por referencia ya que si modificamos el valor de la variable que se utiliza dentro, esta tiene el valor nuevo que fue modificado después de almacenar la función, pero en realidad siguen siendo valores por valor que viven dentro de su ámbito, como un dato de tipo Int o String, donde podemos inyectar dependencias externas dentro de su valor.

Al final Swift, implementa el paradigma funcional en el lenguaje. Por eso este comportamiento.

Funciones enviadas a funciones

Como las funciones pueden asignarse a variables o constantes como tipos de datos, puedo hacer con ellas al menos lo mismo. Por lo tanto, existe la posibilidad de enviar una función como parámetro de otra función.

La aplicación de esta característica es inmensa pero vamos a realizar un ejemplo sencillo para entender cómo funciona. Definamos una función que modifica un texto en base a una función que le indicamos también como parámetro.

func transforma(_ texto: String, con función: (String) -> String) -> String {
    función(texto)
}

A continuación definimos la función cuentaCaracteres(en:) y la utilizamos en la llamada a transforma.

func cuentaCaracteres(en texto: String) -> String {
    "\(texto) tiene \(texto.count) caracter(es)"
}

transforma("Arturo", con: cuentaCaracteres(en:))
// Devuelve "Arturo tiene 6 caracter(es)"

Podemos complicarlo algo más, para ello, vamos a modificar la función transforma para que pueda recibir varios textos y además devuelva un una array de Int en lugar del String actual.

Observamos que la función que recibe como parámetro deberá ser del tipo (String) -> Int y necesitamos añadir un bucle for para hacer la transformación sobre cada uno de los elementos. También redefinimos la función cuentaCaracteres pero a modo de closure.

func transforma(_ textos: [String], con función: (String) -> Int) -> [Int] {
    var temporal = [Int]()
    for texto in textos {
        let númeroCaracteres = función(texto)
        temporal.append(númeroCaracteres)
    }
    return temporal
}

let cuentaCaracteres: (String) -> Int = { $0.count }

let nombres = ["Arturo", "Julio César", "Adolfo"]
transforma(nombres, con: cuentaCaracteres)
// Devuelve [6, 11, 6]

Seguro que ya os empieza a sonar más familiar. Porque esta implementación es la base de las funciones de orden superior map, filter, reduce… que implementa el propio lenguaje Swift sobre el tipo Sequence al que se conforman, por ejemplo los array.

Funciones genéricas

Pero aún nos queda un pequeño detalle y es que las funciones de orden superior soportan varios tipos de entrada y generan varios tipos de salidas. En nuestro caso el tipo era fijo pero, gracias los genéricos, podemos conseguir mayor flexibilidad en nuestra función transforma.

func transforma<Entrada, Salida>(_ textos: [Entrada], con función: (Entrada) -> Salida) -> [Salida] {
    var temporal = [Salida]()
    for texto in textos {
        let númeroCaracteres = función(texto)
        temporal.append(númeroCaracteres)
    }
    return temporal
}

No tenemos más que sustituir los tipos de entrada salida por genéricos. Pero no sólo eso, también podríamos de forma sencilla añadirlo como extension al tipo Sequence y llamarlo como función de orden superior.

extension Sequence {
    func transforma<Salida>(_ función: (Self.Element) -> Salida) -> [Salida] {
        var temporal = [Salida]()
        for texto in self {
            let númeroCaracteres = función(texto)
            temporal.append(númeroCaracteres)
        }
        return temporal
    }
}

nombres.transforma({ $0.count })

Ventajas del lenguaje

Swift es un lenguaje con un montón de capacidades y las funciones son una de las piezas claves que permiten crear código de forma rápida y sencilla. Además las características más avanzadas se construyen con funcionalidades del propio sistema y permiten al desarrollador utilizarlas para conformar sus propias herramientas.

Aunque lleves muchos años en el mundo del desarrollo, siempre está bien volver a la base para analizar cómo se construye el lenguaje. Esperamos que hayas aprendido y/o reforzado tu conocimiento con este artículo. Un saludo y Good Apple Coding.

Etiquetas

Arturo Rivas

Líder técnico especializado en mobile. Analista y desarrollador software. Apasionado de la tecnología, el deporte y la música.

Artículos relacionados

También en
Cerrar
Botón volver arriba
Cerrar
Cerrar