DEV Community

David Goyes
David Goyes

Posted on

Swift #12: Funciones

Las funciones son bloques de código delimitadas por llaves ({) e identificadas por un nombre. A diferencia de los bloques de código usados en los bucles y condicionales, no hay que satisfacer ninguna condición en las funciones para funcionar, sino que las líneas de código de la función se ejecutan siempre que se la invoca.

Una vez que se llama una función, la ejecución del programa continúa dentro de la misma y, cuando termina, regresa a la línea de código que la invocó para continuar con la ejecución anterior.

func f1() { // Declaración y Definición
  let x = 1 + 2 // 2
}

f1() // Invocación

var counter = 0
while counter < 3 { // Invocando tres veces
  f1()
  counter += 1
}
Enter fullscreen mode Exit fullscreen mode

La ventaja de una función es que podemos llamarla desde cualquier parte y siempre ejecutará la misma operación. No se requiere saber cómo funciona internamente, solo hay que pasarle los valores deseados y leer el resultado.

Parámetros

Se puede pasar valores a una función por medio de parámetros dentro de paréntesis. Los parámetros se definen como constantes separadas por comas, así: nombre1: Tipo1, nombre2: Tipo2, .... Cuando se llama la función, los parámetros se vuelven constantes dentro del contexto del bloque de la función.

func f2(a: Int, b: Int) {
  let x = a + b
}

f2(a: 1, b: 2) // provoca que x = 1 + 2
f2(a: 2, b: 5) // provoca que x = 2 + 5
Enter fullscreen mode Exit fullscreen mode

Etiquetas de los argumentos

Cada vez que se llama una función con parámetros se debe poner el nombre de cada parámetro antes del valor del argumento. Estas so las etiquetas de los argumentos.

A veces el nombre del parámetro es claro para el contexto al interior de la función pero para la lectura del llamado. Por esta razón, se puede definir la etiqueta de cada argumento de forma explícita, declarándola antes del nombre del parámetro separados por un espacio.

func f2(first a: Int, second b: Int) {
  let x = a + b
}

f2(first: 1, second: 2) // provoca que x = 1 + 2
Enter fullscreen mode Exit fullscreen mode

Notar que dentro de la función se usan los parámetros a y b, y fuera se ven las etiquetas first y second.

Si, en cambio, se quiere omitir la etiqueta, se puede reemplazar por un guión bajo (_).

func f2(first a: Int, _ b: Int) {
  let x = a + b
}

f2(first: 1, 2) // provoca que x = 1 + 2
Enter fullscreen mode Exit fullscreen mode

Parámetro por referencia inout

Usualmente los argumentos de una función son copiados como constantes en los parámetros de la misma. Sin embargo, se puede pasar una referencia a una variable como argumento a la función para modificar esa variable dentro de la misma. Para esto se debe marcar el parámetro como inout.

func f4() {
  var counter = 0
  f5(a: &counter)
  print(counter) // 1
}
func f5(a: inout Int) {
  a += 1
}
f4() // Imprime: 1
Enter fullscreen mode Exit fullscreen mode

Notar que:

  1. No se puede pasar un valor escalar como argumento. Se necesita pasar una variable que se pueda modificar. En caso de que se trate de pasar un escalar, se tendrá el error: Cannot pass immutable value as inout argument: literals are not mutable.
  2. Se usa el operador & para extraer la referencia a la variable. Si se olvida, se tendrá el error: Passing value of type 'Int' to an inout parameter requires explicit '&'

Valor por defecto de un parámetro

En la declaración de la función, después del tipo de un parámetro, se puede anexar un argumento por defecto con la sintaxis nombre: Tipo = valor_por_defecto. Así, se puede omitir pasar un valor para este parámetro.

func f2(first a: Int, second b: Int = 1) {
  let x = a + b
}

f2(first: 3) // provoca que x = 3 + 1
Enter fullscreen mode Exit fullscreen mode

Valor de retorno

Los valores definidos dentro de una función no pueden ser accedidos por fuera de ella. Se podría decir que están atrapados. Para poder comunicar un resultado con el resto del programa, una función puede retornar un valor con la expresión return que, al llamarse, termina la ejecución de la función.

Para retornar un valor se debe cambiar la firma de la función, especificando el tipo del valor de retorno (e.g. -> Tipo).

func f2(a: Int, b: Int) -> Int {
  return a + b
}

let x1 = f2(a: 1, b: 2) // provoca que x1 = 3
let x2 = f2(a: 2, b: 5) // provoca que x2 = 7
Enter fullscreen mode Exit fullscreen mode

Si la función solo tiene una instrucción que es retornar un valor, se puede omitir la palabra clave return.

func f3(a: Int, b: Int) -> Int {
  a + b
}
Enter fullscreen mode Exit fullscreen mode

Sobrecarga de funciones

No se puede crear más de una función con la mismo nombre; nombre, tipo y número de parámetros. Sin embargo, se puede crear más de una función con el mismo nombre y variar sus parámetros: en tipo y cantidad. A esto se le conoce como Sobrecarga de funciones.

func f(value: Int) -> Int {
  value + 1
}
func f(value: String) -> Int {
  value.count
}
print(f(value: "Hola")) // Infiere que usa f(value: String)
print(f(value: 1)) // Infiere que usa f(value: Int)
Enter fullscreen mode Exit fullscreen mode

También se puede sobrecargar el tipo del valor de retorno, sin embargo, si se deja la puerta abierta para que el compilador de Swift infiera el tipo de función que debe ejecutar, se tendrá el error: Ambiguous use of '...'. Por ejemplo:

func f(value: Int) -> Int {
  value + 1
}
func f(value: Int) -> Double {
    Double(value) + 1.0
}
let result = f(value: 1) // Ambiguous use of 'f(value:)
print(result)
Enter fullscreen mode Exit fullscreen mode

Para resolver este problema, hay que suministrar un poco más de información que le permita al compilador deducir cuál función se debe usar. Por ejemplo:

func f(value: Int) -> Int {
  value + 1
}
func f(value: Int) -> Double {
    Double(value) + 1.0
}
let result: Double = f(value: 1) // ✅
print(result)
Enter fullscreen mode Exit fullscreen mode

Funciones genéricas

Consideremos el escenario de dos funciones con el mismo nombre, el mismo valor de retorno y un solo parámetro, solo que sería de un tipo diferente en cada función. Luego, el sistema seleccionará la función a ejecutar dependiendo del tipo del argumento.

func f(value: Int) -> String {
  "Valor: \(value)"
}
func f(value: String) -> String {
  "Valor: \(value)"
}
let result1 = f(value: 1)
let result2 = f(value: "Hola")
Enter fullscreen mode Exit fullscreen mode

En el ejemplo anterior, las dos funciones tenían el mismo cuerpo. En este caso, podemos evitar duplicar código haciendo una función genérica:

func f<T>(value: T) -> String {
  "Valor: \(value)"
}
let result1 = f(value: 1) // Valor: 1
let result2 = f(value: "Hola") // Valor: Hola
Enter fullscreen mode Exit fullscreen mode

Un tipo genérico funciona como plantilla. Cuando se invoca la función, esta plantilla se convierte en el tipo de valor recibido.

Puede haber más de un tipo de dato plantilla, por ejemplo:

func f<T, U>(value1: T, value2: U) -> String {
  "Valores: \(value1) \(value2)"
}
let result1 = f(value1: 1, value2: 0.5) // Valores: 1 0.5
let result2 = f(value1: "Hola", value2: "Chao") // Valores: Hola Chao
Enter fullscreen mode Exit fullscreen mode

Biblioteca estándar

La biblioteca estándar de Swift incluye varios operadores, tipos primitivos y funciones predefinidas como:

  1. print(String): Imprime un string en la consola.
  2. abs(Int): Retorna el valor absoluto de un entero.
  3. max(Values): Retorna el valor más grande
  4. min(Values): Retorna el valor más pequeño.

Funciones para detener la ejecución del programa

  1. fatalError(String): Detiene la ejecución de la aplicación e imprime un mensaje en la consola.
  2. precondition(Bool, String): Detiene la ejecución de la aplicación si la condición es false, e imprime un mensaje en consola.

Funciones para crear colecciones

  1. stride(from: Value, through: Value, by: Value): Retorna una colección de valores desde from hasta through (inclusive) en intervalos de by.
  2. stride(from: Value, to: Value, by: Value): Retorna una colección de valores desde from hasta antes de through (excluyéndolo) en intervalos de by.
  3. repeatElement(Value, count: Int): Retorna una colección repitiendo count veces el valor Value.
  4. zip(Collection, Collection): Retorna una colección de tuplas con los valores de los argumentos en orden secuencial.

Alcance de una función (Scope)

Un bloque de código se define como un conjunto de variables e instrucciones envueltas entre llaves (i.e. { ... }). Las variables declaradas dentro de un bloque no pueden ser accedidas desde otro bloque diferente. La región del código que tiene acceso a estas variables se conoce como "Alcance" o "Scope" del bloque.

Las variables definidas en un scope global pueden ser accedidas desde un scope local, pero no al revés.

Top comments (0)