Swift – Funciones

Concepto de función

Al igual que en cualquier otro lenguaje de programación, uno de los puntos básicos que todo desarrollador Swift debe conocer es la manera de trabajar con funciones.

Podríamos decir que forma parte del núcleo de un lenguaje y provee un sinfín de beneficios al momento de escribir código claro y mantenible.

Una función es una porción de código que se encapsula bajo un nombre y puede ser invocado las veces que sea necesario.

La primer ventaja que ofrece es la posibilidad de reutilizar esa funcionalidad evitando tener que escribir el algoritmo repetidas veces a lo largo del programa. Se espera que el nombre asignado a la función sea descriptivo y relacionado a la tarea que la misma realiza.

La forma en la que las funciones están pensadas en Swift le da al desarrollador una flexibilidad considerable al momento de utilizarlas.

En este sentido, es posible escribir funciones súper básicas como así también otras mucho más rebuscadas.

Para poder escribir una función, se utiliza la palabra reservada funcseguida del nombre de la misma. La forma más básica sería:

func saludar() {
print("Hola mundo!")
}

saludar()

//Devuelve:
//Hola mundo!

Parámetros y valores de retorno

Asimismo, las funciones pueden recibir valores de entrada para poder trabajar en función a ellos, los cuales son conocidos como parámetros. De la misma manera, una función puede devolver un valor a quien lo invoca una vez finalizada su ejecución, conocido como valor de retorno.

//Función con un parámetro
func saludar(nombre: String) {
print("Hola \(nombre)!")
}

saludar(nombre: "Gabriel")

//Devuelve:
//Hola Gabriel!

//Función con un parámetro y un valor de retorno
func saludarA(nombre: String) -> String {
return "Hola \(nombre)!"
}

let saludo = saludarA(nombre: "Gabriel")
print(saludo)

//Devuelve:
//Hola Gabriel!

Como muestran los ejemplos anteriores, la invocación a una función se realiza escribiendo el nombre de la misma seguido de paréntesis. Si la función posee parámetros, los mismos se incluyen dentro de los paréntesis y si la función devuelve un valor se utiliza la flecha -> seguida del tipo de dato a devolver. Este tipo puede ser cualquiera, ya sea un StringInt, una tupla, una clase o struct que definamos nosotros, un opcional, etc. Asimismo, se debe utilizar algún mecanismo para recibir ese dato (por ejemplo, guardarlo en una constante o variable).

Los valores de entrada pasados a una función al momento de invocarla se denominan Argumentos. Estos argumentos deben coincidir con el tipo de dato de los parámetros esperados de la función.
Por otro lado, el conjunto conformado por el nombre de la función, los parámetros y su valor de retorno conforman la definición de la función, y no puede haber en un mismo código dos funciones con la misma definición.
Cuando tenemos dos o más funciones con el mismo nombre pero distinta definición, decimos que esa función está sobrecargada o que existe una sobrecarga de la misma.

En el caso en que se desee usar dos o más parámetros, se deben separar por una coma en la definición de la función:

//Función con dos parámetros
func saludar(nombre: String, edad: Int) {
print("Felices \(edad) años \(nombre)!")
}

saludar(nombre: "Gabriel", edad: 29)

//Devuelve
//Felices 29 años Gabriel!

Devolver más de un dato

La forma que nos brinda Swift para retornar más de un valor es mediante las tuplas. En este caso, al momento de invocar la función podemos hacer referencia a cualquiera de los datos obtenidos simplemente utilizando su nombre o posición en la tupla:

//Función que devuelve más de un dato
func minMax(array: [Int]) -> (min: Int, max: Int) {
var minimo = array[0]
var maximo = array[0]

for valor in array[1..<array.count] {
if valor < minimo { minimo = valor } else if valor > maximo {
maximo = valor
}
}

return (minimo, maximo)
}

let notas = minMax(array: [1, 2, 10, 8, 5])
print("La nota mas baja es \(notas.min) y la mas alta es \(notas.max)")

//Devuelve:
//La nota más baja es 1 y la más alta es 10

Etiquetas de argumento

Cuando especificamos nuestros parámetros podemos definir una etiqueta de argumento (argument label), la cual se utiliza al momento de llamar a la función y se especifica delante del nombre del parámetro en la definición de la misma. En este sentido, el parámetro se usa internamente en la implementación de la función cuando se requiere hacer uso de él.

//Etiquetas de argumento
func saludar(a nombre: String, porCumplir edad: Int){
print("Felices \(edad) años \(nombre)!")
}

saludar(a: "Gabriel", porCumplir: 30)

//Devuelve:
//Felices 30 años Gabriel!

Como se aprecia en el ejemplo, se está usando un argument label por cada parámetro. Internamente en la función se usa el nombre del parámetro, pero al momento de invocarla solo se usan las etiquetas de argumento. Nótese lo claro que resulta entender lo que hace la función saludar(a:porCumplir:) al momento de invocarla.

En el caso de que no se especifique una etiqueta de argumento, el mismo nombre de parámetro funciona como nombre de parámetro y etiqueta. Sin embargo, si se desea omitir por completo la etiqueta al momento de invocar a la función, se utiliza un guión bajo (_):

//Omitiendo argument label
func sumar(_ primerNumero: Int, _ segundoNumero:Int) -> Int
{
return primerNumero + segundoNumero
}

print("El resultado de 4 + 5 es \(sumar(4,5))")

//Devuelve:
//El resultado de 4 + 5 es 9

Parámetros con valores por defecto

Otra posibilidad que nos habilita Swift es la de asignar un valor por defecto a los parámetros, de forma tal que obtengan ese valor en caso de que no se le pase ninguno al momento de invocar a la función.

En el caso de utilizar esta metodología, se recomienda listar primero los parámetros que no tienen valores por defecto (que en la mayoría de los casos son más representativos para la función) y luego los que sí tienen.

//Parámetros por defecto
func saludar(a nombre:String, cumpleAños:Bool = false) {
if cumpleAños {
print("Feliz cumpleaños \(nombre)")
}
else {
print("Buen día \(nombre)")
}
}

saludar(a: "Gabriel", cumpleAños: true)
saludar(a: "Gabriel")

//Devuelve:
//Feliz cumpleaños Gabriel
//Buen día Gabriel

Como se aprecia en el ejemplo anterior, al tener un parámetro opcional la función puede ser llamada incluyendo ese argumento o no.

Parámetros variádicos

Este tipo de parámetros permite especificar una cantidad de cero o más valores de un tipo determinado. Se utiliza para dar flexibilidad al momento de usar la función para pasar una cantidad variables de valores sabiendo que la misma siempre va a trabajar correctamente.

Para indicar que un parámetro es variádico, se usan 3 puntos suspensivos luego del tipo del parámetro, e internamente ese valor se usa como un array.

//Parámetros variádicos
func sumar(numeros:Int…) -> Int {
var suma:Int=0

for valor in numeros {
suma = suma+valor
}

return suma
}

print(“La suma es igual a \(sumar(numeros: 4,6,2,3,10,6,7,5,4))”)

//Devuelve:
//La suma es igual a 47

Las funciones pueden tener 1 solo parámetro variádico. En caso de que se necesite usar varios parámetros, siendo uno solo de ellos variádico, éste debe quedar al final.

Parámetros in-out

Los parámetros que usamos en nuestras funciones son constantes, es decir que no pueden ser modificadas internamente en la función. Si por ejemplo quisiéramos agregar un número más al ejemplo anterior, recibiríamos este error:

error: cannot use mutating member on immutable value: ‘numeros’ is a ‘let’ constant

 numeros.append(3)

Esta característica funciona así de manera intencionada para evitar que se cambie el valor de un parámetro por error, lo que puede causar comportamientos extraños a lo largo del código y genere una alta dificultad para encontrar el motivo.

De todas maneras, si lo que queremos es modificar los valores de los parámetros internamente en la función y que ese cambio persista incluso cuando la función termina, debemos usar el modificador inout entre el nombre del parámetro y su tipo.

Existen unas reglas a tener en cuenta:

  • Al momento de invocar la función, si el parámetro es inout, solo pueden pasarse variables. Constantes o literales no están permitidos ya que justamente no pueden ser modificados
  • Los parámetros variádicos no pueden ser inout
  • Los parámetros inout no pueden tener valores por defecto
  • Se debe utilizar un ampersand (&) delante de la variable en el llamado a la función
//Parámetros inout
func cambiarDosEnteros(_ a: inout Int, _ b: inout Int) {
let auxiliar = a
a = b
b = auxiliar
}

var numero1 = 5
var numero2 = 8

print("Numero1: \(numero1)")
print("Numero2: \(numero2)")

cambiarDosEnteros(&numero1, &numero2)

print("Numero1: \(numero1)")
print("Numero2: \(numero2)")

//Devuelve
//Numero1: 5
//Numero2: 8

//Numero1: 8
//Numero2: 5

El uso del ampersand (&) viene del lenguaje C y se utiliza para indicar la posición en memoria de la variable. Como el objetivo es modificar el valor del parámetro dentro de la función, se pasa su dirección para poder introducir allí el nuevo dato.

Tipo de una función

Todas las funciones tienen su propio tipo, que está formado por el tipo de sus parámetros y el tipo de su valor de retorno. Veamos algunos ejemplos:

func saludar() {
print("Hola mundo!")
}

El tipo de esta función es () -> Void, o lo que es lo mismo “una función que no recibe parámetros y devuelve Void”

func saludarA(nombre: String) -> String {
return "Hola \(nombre)!"
}

El tipo de esta función es (String) -> String, o lo que es lo mismo “una función que recibe un parámetro del tipo String y devuelve un String”

En este sentido, se pueden definir variables o constantes del tipo de una función. Por ejemplo:

//Tipos de una función
func sumarDosNumeros(_ a: Int, _ b: Int) -> Int {
return a + b
}

func multiplicarDosNumeros(_ a: Int, _ b: Int) -> Int {
return a * b
}

var calculo:(Int,Int) -> Int = sumarDosNumeros

print("El resultado de sumar 3 + 7 es \(calculo(3,7))")

calculo = multiplicarDosNumeros

print("El resultado de multiplicar 3 x 7 es \(calculo(3,7))")

//Devuelve:
//El resultado de sumar 3 + 7 es 10
//El resultado de multiplicar 3 x 7 es 21

Lo que hicimos fue definir dos funciones distintas pero del mismo tipo: ambas reciben dos parámetros Int y devuelven un Int. Luego, definimos una variable de ese tipo (Int, Int) -> Int al cual primero lo igualamos a la primer función y luego a la segunda.

De la misma manera, se puede utilizar a las funciones como parámetros, definiéndolos con el tipo correspondiente. Esto permite que parte de la implementación de la función se la deje a la clase o struct que la invoca. Más adelante veremos cómo funcionan las clases en swift.

func imprimirResultado(_ operacion:(Int,Int) -> Int, _ a:Int, _ b:Int ) {
print("El resultado es \(operacion(a,b))")
}

imprimirResultado(sumarDosNumeros, 10, 60)

//Devuelve:
//El resultado es 70

En el ejemplo, la función imprimirResultado(_:_:_:) solo hace eso, imprime un resultado de una operación que no se conoce al momento de la implementación pero que se recibe por parámetro. Recién al momento de llamar a la función se sabe que lo que se quiere es sumar los números pasados a la misma.

Ejemplos completos

//Funcion sin parametros
func saludar() {
    print("Hola mundo!")
}

saludar()

//Devuelve:
//Hola mundo!

//Función con un parámetro
func saludar(nombre: String) {
    print("Hola \(nombre)!")
}

saludar(nombre: "Gabriel")

//Devuelve:
//Hola Gabriel!

//Función con un parámetro y un valor de retorno
func saludarA(nombre: String) -> String {
    return "Hola \(nombre)!"
}

let saludo = saludarA(nombre: "Gabriel")
print(saludo)

//Función con dos parámetros
func saludar(nombre: String, edad: Int) {
    
    print("Felices \(edad) años \(nombre)!")
}

saludar(nombre: "Gabriel", edad: 29)

//Devuelve
//Felices 29 años Gabriel!

//Función que devuelve más de un dato
func minMax(array: [Int]) -> (min: Int, max: Int) {
    var minimo = array[0]
    var maximo = array[0]
    for valor in array[1..<array.count] {
        if valor < minimo {
            minimo = valor
        } else if valor > maximo {
            maximo = valor
        }
    }
    return (minimo, maximo)
}

let notas = minMax(array: [1, 2, 10, 8, 5])
print("La nota más baja es \(notas.min) y la más alta es \(notas.max)")

//Devuelve:
//La nota más baja es 1 y la más alta es 10

//Etiquetas de argumento
func saludar(a nombre: String, porCumplir edad: Int){
    print("Felices \(edad) años \(nombre)!")
}

saludar(a: "Gabriel", porCumplir: 30)

//Devuelve:
//Felices 30 años Gabriel!

//Omitiendo argument label
func sumar(_ primerNumero: Int, _ segundoNumero:Int) -> Int
{
    return primerNumero + segundoNumero
}

print("El resultado de 4 + 5 es \(sumar(4,5))")

//Devuelve:
//El resultado de 4 + 5 es 9

//Parámetros por defecto
func saludar(a nombre:String, cumpleAños:Bool = false) {
    if cumpleAños {
        print("Feliz cumpleaños \(nombre)")
    }
    else {
        print("Buen día \(nombre)")
    }
}

saludar(a: "Gabriel", cumpleAños: true)
saludar(a: "Gabriel")

//Devuelve:
//Feliz cumpleaños Gabriel
//Buen día Gabriel

//Parámetros variádicos
func sumar(numeros:Int...) -> Int {
    var suma:Int=0
    for valor in numeros {
        suma = suma+valor
    }
    return suma
}

print("La suma es igual a \(sumar(numeros: 4,6,2,3,10,6,7,5,4))")

//Devuelve:
//La suma es igual a 47

//Parámetros inout
func cambiarDosEnteros(_ a: inout Int, _ b: inout Int) {
    let auxiliar = a
    a = b
    b = auxiliar
}

var numero1=5
var numero2=8

print("Numero1: \(numero1)")
print("Numero2: \(numero2)")

cambiarDosEnteros(&numero1, &numero2)

print("Numero1: \(numero1)")
print("Numero2: \(numero2)")

//Devuelve
//Numero1: 5
//Numero2: 8
//Numero1: 8
//Numero2: 5


//Tipos de una función
func sumarDosNumeros(_ a: Int, _ b: Int) -> Int {
    return a + b
}
func multiplicarDosNumeros(_ a: Int, _ b: Int) -> Int {
    return a * b
}

var calculo:(Int,Int) -> Int = sumarDosNumeros

print("El resultado de sumar 3 + 7 es \(calculo(3,7))")

calculo = multiplicarDosNumeros
print("El resultado de multiplicar 3 x 7 es \(calculo(3,7))")

//Devuelve:
//El resultado de sumar 3 + 7 es 10
//El resultado de multiplicar 3 x 7 es 21

func imprimirResultado(_ operacion:(Int,Int)->Int, _ a:Int, _ b:Int ) {
    print("El resultado es \(operacion(a,b))")
}

imprimirResultado(sumarDosNumeros, 10, 60)

//Devuelve:
//El resultado es 70