5  Funciones

5.1 Creación de funciones

Una función asocia un nombre a un bloque de código de manera que cada vez que se invoca a la función se ejecuta el bloque de código asociado.

Para crear una función se utiliza la siguiente sintaxis

function nombre(parámetros)
   bloque de código
end

Nota

La indentación del bloque de código no es necesaria pero es una buena práctica.

Para invocar una función basta con escribir su nombre y pasarle entre paréntesis los valores de los parámetros (argumentos) separados por comas.

5.1.1 Ejemplo de creación de funciones

julia> function saludo()  # Función sin parámetros
         println("¡Bienvenido!")
       end
saludo (generic function with 1 method)

julia> saludo()
¡Bienvenido!

julia> typeof(saludo)
typeof(saludo) (singleton type of function saludo, subtype of Function)

5.2 Parámetros y argumentos de una función

Una función puede recibir valores cuando se invoca a través de unas variables conocidas como parámetros que se definen entre paréntesis y separados por comas en la declaración de la función. En el cuerpo de la función se pueden usar estos parámetros como si fuesen variables.

Los valores que se pasan a la función en una llamada o invocación concreta de ella se conocen como argumentos y se asocian a los parámetros de la declaración de la función.

julia> function calificacion(nota)  # Función con un parámetro
         if nota < 5
           println("Suspenso")
         else
           println("Aprobado")
         end
       end
calificacion (generic function with 1 method)

julia> calificacion(7)
Aprobado

5.2.1 Paso de argumentos a una función

Los argumentos se pueden pasar de dos formas:

  • Argumentos posicionales: Se asocian a los parámetros de la función en el mismo orden que aparecen en la definición de la función.
  • Argumentos nominales: Se indica explícitamente el nombre del parámetro al que se asocia un argumento de la forma parametro = argumento.

Cuando una función tiente parámetros posicionales y como nominales, los posicionales deben indicarse primero y los nominales después, separando ambos tipos de parámetros por punto y coma ;.

5.2.2 Ejemplo de paso de argumentos a una función

julia> function saludo(nombre, apellidos; ciudad)
       println("¡Hola $nombre $apellidos, bienvenido a $(ciudad)!")
       end
saludo (generic function with 1 method)

julia> saludo("Alfredo", "Sánchez", ciudad = "Madrid")
¡Hola Alfredo Sánchez, bienvenido a Madrid!

julia> saludo("Alfredo", ciudad = "Madrid",  "Sánchez")
¡Hola Alfredo Sánchez, bienvenido a Madrid!

5.2.3 Argumentos por defecto

En la definición de una función se puede asignar a cada parámetro un argumento por defecto, de manera que si se invoca la función sin proporcionar ningún argumento para ese parámetro, se utiliza el argumento por defecto.

El valor por defecto de un parámetro se indica con la siguiente sintaxis parámetro = valor.

julia> function saludo(nombre, apellidos, ciudad = "Madrid")
         println("¡Hola $nombre $apellidos, bienvenido a $(ciudad)!")
       end
saludo (generic function with 2 methods)

julia> saludo("Alfredo", "Sánchez")
¡Hola Alfredo Sánchez, bienvenido a Madrid!

julia> saludo("Pepito", "Grillo", "Barcelona")
¡Hola Pepito Grillo, bienvenido a Barcelona!

5.2.4 Funciones con un número variable de argumentos

Julia permite definir funciones que pueden llamarse con un número variable de argumentos. Para que una función pueda recibir un número variable de argumentos hay que poner tres puntos suspensivos ... al final de último parámetro posicional.

Cuando se llame a la función los argumentos se irán asociando a los parámetros posicionales en orden y el último parámetro se asociará a una tupla con el resto de argumentos en la llamada.

julia> function media(x...)
       println("Media de ", x)
       sum(x) / length(x)
       end
media (generic function with 1 method)

julia> media(1, 2, 3, 4)
Media de (1, 2, 3, 4)
2.5

5.2.5 Parámetros con tipo

Aunque Julia es un lenguaje de tipado dinámico también permite fijar el tipo de los parámetros de una una función. Esto permite definir diferentes variantes (métodos) de una misma función dependiendo del tipo de los argumentos, así como detectar errores cuando se llama a la función con argumentos de distinto tipo.

Para indicar el tipo de los parámetros de una función se utiliza la sintaxis parametro::tipo.

Advertencia

Conviene no restringir demasiado el tipo de los parámetros de una función. Se debe elegir el tipo más general en la jerarquía de tipos para el que tiene sentido la función.

5.2.6 Ejemplo de parámetros con tipo

julia> function sumar(x::Number, y::Number)
       x + y
       end
sumar (generic function with 1 method)

julia> function sumar(x::String, y::String)
       x * y
       end
sumar (generic function with 2 methods)

julia> sumar(1, 2)
3

julia> sumar(1.5, 2.5)
4.0

julia> sumar("Hola", "Julia")
"HolaJulia"

5.2.7 Paso de argumentos por asignación

En Julia los argumentos se pasan a una función por asignación, es decir, se asignan a los parámetros de la función como si fuesen variables locales. De este modo, cuando los argumentos son objetos mutables (arrays, diccionarios, etc.) se pasa al parámetro una referencia al objeto, de manera que cualquier cambio que se haga en la función mediante el parámetro asociado afectará al objeto original y serán visibles fuera de ella.

julia> function matricular(curso, asignatura)
       push!(curso, asignatura)
       end
matricular (generic function with 1 method)

julia> primer_curso = [];

julia> matricular(primer_curso, "Álgebra Lineal");

julia> matricular(primer_curso, "Programación");

julia> primer_curso
2-element Vector{Any}:
 "Álgebra Lineal"
 "Programación"

5.2.8 Ámbito de los parámetros de una función

Los parámetros y las variables declaradas dentro de una función son de ámbito local, mientras que las variable definidas fuera de funciones son de ámbito ámbito global.

Tanto los parámetros como las variables del ámbito local de una función sólo están accesibles durante la ejecución de la función. Es decir, cuando termina la ejecución de la función estas variables desaparecen y no son accesibles desde fuera de la función.

Si en el ámbito local de una función existe una variable que también existe en el ámbito global, durante la ejecución de la función la variable global queda eclipsada por la variable local y no es accesible hasta que finaliza la ejecución de la función.

5.2.9 Ejemplo del ámbito de los parámetros de una función

julia> lenguaje = "Python";

julia> function saludo(nombre)
       lenguaje = "Julia"
       println("¡Hola $(nombre), bienvenido a $(lenguaje)!")
       end
saludo (generic function with 3 methods)

julia> saludo("Alf")
¡Hola Alf, bienvenido a Julia!

julia> lenguaje
"Python"

julia> nombre
ERROR: UndefVarError: nombre not defined

5.3 Retorno de una función

Una función devuelve siempre el valor de la última expresión evaluada en su cuerpo. Sin embargo, puede devolverse cualquier otro valor indicándolo detrás de la palabra reservada return. Cuando el flujo de ejecución de la función alcanza esta palabra, la ejecución de la función termina y se devuelve el valor que la acompaña.

Si una función no devuelve ningún valor se puede escribir la palabra return sin nada más.

Cuando se desea devolver más de un valor se puede pueden indicar separados por comas y la función devolverá la tupla formada por esos valores.

5.3.1 Ejemplo de retorno de una función

julia> function area_triangulo(base, altura)
         return base * altura / 2  # Devuelve un valor
       end
area_triangulo (generic function with 1 method)

julia> area_triangulo(3, 4)
6.0

julia> function area_perimetro_circulo(r)
         return π * r ^ 2, 2π * r  # Devuelve dos valores 
       end
area_perimetro_circulo (generic function with 1 method)

julia> area_perimetro_circulo(1)
(3.141592653589793, 6.283185307179586)

5.4 Funciones compactas

Cuando el cuerpo de una función es una única expresión se puede definir la función de forma mucho más compacta de la siguiente manera:

nombre(parametros) = expresión

El valor que devuelve la función es el resultado de evaluar la expresión.

Esta forma de definir funciones es muy habitual para funciones matemáticas.

julia> area_triangulo(b, a) = b * a / 2
area_triangulo (generic function with 1 method)

julia> area_triangulo(3, 4)
6.0

julia> valor_absoluto(x) = x < 0 ? -x : x
valor_absoluto (generic function with 1 method)

julia> valor_absoluto(-1)
1

5.5 Funciones como objetos

En Julia las funciones son objetos como el resto de tipos de datos, de manera que es posible asignar una función a una variable y luego utilizar la variable para hacer la llamada a la función, pasar una función como argumento de otra función, o que una función devuelva otra función.

julia> suma(x, y) = x + y
suma (generic function with 1 method)

julia> adicion = suma
suma (generic function with 1 method)

julia> adicion(1, 2)
3

julia> calculadora(operador, x, y) = operador(x, y) 
calculadora (generic function with 1 method)

julia> calculadora(suma, 1, 2)
3

5.6 Funciones anónimas

Julia permite también definir funciones sin nombre. Para ello se utiliza la siguiente sintaxis.

(parametros) -> expresión

El principal uso de las funciones anónimas es para pasarlas como argumentos de otras funciones.

ulia> calculadora(operador, x, y) = operador(x, y) 
calculadora (generic function with 1 method)

julia> calculadora((x, y) -> x + y, 1, 2)
3

julia> calculadora((x, y) -> x - y, 1, 2)
-1

5.7 Funciones asociadas a operadores

En Julia los operadores tienen asociadas funciones que son llamadas por el intérprete cuando se evalúa una expresión con operadores.

julia> +(1, 2, 3)  # Equivalente a 1 + 2 + 3
6

julia>= +
+ (generic function with 208 methods)

julia> (1, 2, 3)
6

5.8 Funciones recursivas

Una función recursiva es una función que en su cuerpo contiene alguna llama a si misma.

La recursión es una práctica común en la mayoría de los lenguajes de programación ya que permite resolver las tareas recursivas de manera más natural.

Para garantizar el final de una función recursiva, las sucesivas llamadas tienen que reducir el grado de complejidad del problema, hasta que este pueda resolverse directamente sin necesidad de volver a llamar a la función.

Precaución

La recursión es una técnica que suele ser poco eficiente computacionalmente y conviene evitarla siempre que sea posible.

5.8.1 Ejemplo de funciones recursivas

julia> function factorial(n::Integer)
         if n <= 1
           return 1
         else
           return n * factorial(n-1)
         end
       end
factorial (generic function with 1 method)

julia> factorial(4)
24

julia> fib(n::Integer) = n  2 ? 1 : fib(n - 1) + fib(n - 2)
fib (generic function with 1 method)

julia> fib(10)
55