2  Básicos de Julia

Autor/a

Iraitz Montalbán

En estos primeros ejemplos veremos los rudimentos de la sintaxis de Julia. Y empezaremos con los básicos aunque dado que existen amplias coincidencias entre Julia y Python, asumiendo que el lector conoce las estructuras base de Python (listas, diccionarios, etc..) nos centraremos en las grandes diferencias.

2.1 Variables

Veamos con una de las primeras acciones que será la definición de variables.

x = 2
2

Como vemos Julia tiene tipado dinámico, no tenemos por qué definir el tipo de datos asociado al valor que hemos introducido.

typeof(x)
Int64

Podemos hacer lo mismo con múltiples tipos

x = 2 + 1im
typeof(x)
Complex{Int64}

Aunque podemos incluir el tipado si fuera necesario.

y::Float16 = 120
typeof(y)
Float16

Esto impedirá que hagamos cosas como

y = "hola"

con un error MethodError: Cannot convert an object of type String to an object of type Float16 en nuestro caso, ya que el tipo declarado de la variable y el valor a asociar no coinciden. Trabajaremos principalmente con los tipos habituales

  • Enteros: Int64
  • Números reales: Float64
  • Binarios (verdadero o falso): Bool
  • Cadenas de caracteres: String

Podemos crear tipos específicos para nuestras necesidades basado en esos tipos básicos.

struct Usuario
    nombre::String
    apellido::String
    anio_nacimiento::Int64
end

Usuario("Iraitz","Montalbán", 1984)
Usuario("Iraitz", "Montalbán", 1984)

Como vemos , Julia tiene por costumbre imprimir el valor de la última línea ejecutada. Por definición son estructuras inmutables, por lo que si queremos variar sus datos una vez inicializados deberemos indicarlo expresamente.

mutable struct UsuarioMutable
    nombre::String
    apellido::String
    anio_nacimiento::Int64
end

iraitz = UsuarioMutable("Iraitz","Montalban", 1984)
iraitz.apellido = "Montalbán"
"Montalbán"

Las variables booleanas nos permiten operaciones lógicas:

  • !: NOT
  • &&: AND
  • ||: OR
!true
false
true && false
false

O de pertenencia a un grupo o tipo, igualdad == y desigualdad !=, <, etc.

6 isa Real
true

2.2 Arrays

Las listas de datos funcionan de forma muy similar a las listas en Python.

a = [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

Vemos que el tipo por defecto se llama Vector{} y en corchetes marca nuestro tipo para los elementos. Esto implica que si un elemento requiere un tipado mayor, este se les asignará también al resto, siendo Any (cualquiera) la opción más general.

a = [1, 2, "hola"]
3-element Vector{Any}:
 1
 2
  "hola"

Podemos separar en filas nuestros elementos y así generar un vector de dos dimensiones o matriz.

a = [1 2 3; 4 5 6]
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

Y podemos así extender a las dimensiones que necesitemos nuestros elementos multidimensionales. El acceso a los datos se realiza por índices o rangos, teniendo en cuenta el número de índices de nuestro vector. Quizás una de las cuestiones más relevantes viniendo de lenguajes como Python es que Julia cuenta lso elementos iniciando en 1 en lugar de 0. Por lo tanto pedir el elemento a[0] nos dará un error.

a[1]
1
a[1:2]
2-element Vector{Int64}:
 1
 4
a[:, 1:2]
2×2 Matrix{Int64}:
 1  2
 4  5
a[2, 2]
5

Tampoco funcionarán los índices negativos ya que deberemos usar la palabra reservada end.

a[end, end]
6

2.3 Funciones

Otro aspecto clave son las funciones. La lógica de nuestro programa que toma variables y devuelve variables basado en operaciones lógicas.

function nombre_funcion(arg1, arg2)
    resultado = hacemos cosas con arg1 y arg2
    return resultado
end

También permite la definición mediate asignación, a modo de función lambda f_name(arg1, arg2) = hacemos cosas con arg1 y arg2 aunque por claridad emplearemos el modelo anterior.

Julia permite especificar el tipo de argumentos haciendo que dos funciones con el mismo nombre apliquen distinta lógica en base a los argumentos de entrada y su tipo.

function redondeo(x::Int64)
    print("Es un entero")
    return x
end

function redondeo(x::Float64)
    print("Es un número con decimales")
    return round(x)
end

methods(redondeo)
# 2 methods for generic function redondeo from Main.Notebook:

Esto nos permite invocar a uno u otro únicamente en base al tipado.

x = Float64(18)

redondeo(x)
Es un número con decimales
18.0

En estos casos, si el tipado no coincide con ninguna de las definiciones de la función, Julia nos alertará con un error.

Podemos también definir valores por defecto en los argumentos de entrada y devolver más de un elemento.

function suma_y_multiplica(x::Int64, y::Int64 = 10)
    suma = x + y
    mult = x * y
    return suma, mult
end

suma_y_multiplica(5)
(15, 50)
suma_y_multiplica(5, 5)
(10, 25)

En caso de querer utilizar argumentos clave (keyword) estos se separan mediante el carácter ; y siempre deben disponer de un valor por defecto.

function suma_y_multiplica(x::Int64; y::Int64 = 10)
    suma = x + y
    mult = x * y
    return suma, mult
end

suma_y_multiplica(5, y=7)
(12, 35)

Por último, en Julia existe la convención de añadir el carácter ! en caso de que la función altere o modifique alguno de sus argumentos.

function suma_uno!(V)
    for i in eachindex(V)
        V[i] += 1
    end
    return nothing
end
suma_uno! (generic function with 1 method)

No devolvemos nada pero si evaluamos el contenido de nuestro dato inicial, veremos que ha sido modificado.

datos = [1, 2, 3]

suma_uno!(datos)

datos
3-element Vector{Int64}:
 2
 3
 4

Existen toda una serie de funciones que podemos emplear directamente desde la consola. Tenemos operadores básicos como suma o multiplicación, y otras funcionalidades no tan básicas como la selección de los primeros o últimos elementos de un vector, la creación de números aleatorios o el poder fijar una semilla para dicha generación aleatoria.

2.3.1 Macros

Como último apunte, veréis que existen sentencias que iniciamos con el carácter @. Estas son conocidas como macros, operaciones que nos permiten simplificar la sintaxis y que en muchos casos hacen referencia a funciones con el mismo nombre.

@time print("Hemos tardado en imprimir esta línea")
Hemos tardado en imprimir esta línea  0.000030 seconds (10 allocations: 64.281 KiB)
macro es_entero(x)
   if typeof(x) == Int64  
      return "Es un entero"
   end
   return "No es un entero"
end

@es_entero 5
"Es un entero"

Más que listar todas estas operaciones y estructuras disponibles, iremos viendo con ejemplos las más usuales según avancemos en los materiales.