Skip to the content.

Instalación

Julia es gratuito y de código abierto (no sólo eso sino que, como remarcaremos reiteradamente, la mayor parte del código ¡está escrito en Julia!). La instalación es muy sencilla, siguiendo las instrucciones en el sitio de Descargas. La opción recomendada es abrir una terminal y correr el siguiente código (requiere conexión a internet):

 $ curl -fsSL https://install.julialang.org | sh

Esto instala juliaup que es una aplicación para mantener y actualizar Julia y además corre juliaup instalando la versión actual de Julia. En el futuro se podrá actualizar a la versión más reciente de Julia corriendo en una terminal:

 $ juliaup update

La segunda alternativa es bajar un instalador o un binario específico para el sistema operativo en el que se quiera instalar (más abajo, en la misma página). Esto sólo instala Julia. Es posible que esta sea la mejor opción para Windows.

En cualquiera de los casos se obtendrá una instalación del núcleo de Julia (lo que se llama Julia Base) más algunas librerías consideradas básicas (Statistics, LinearAlgebra, etc.) que se incluyen en el paquete básico pero deben importarse en caso de querer usarlas. Existen muchísimos otros paquetes, que pueden instalarse aparte (desde Julia).

La (o el?) REPL

Una vez realizada la instalación lo más común (y recomendable) es usar Julia a través de la REPL (Read, Eval, Print Loop), a la que llamaremos simplemente “consola”. En Windows probablemente se genere un ícono de Julia, que permitirá abrir la consola. En Linux y MacOS debería bastar correr el comando julia en una terminal.

La REPL es una consola interactiva que nos permite escribir código (en pocas líneas) y ejecutarlo, correr archivos .jl, importar módulos, instalar módulos, acceder a un help y algunas cosas más. Un archivo con código Julia puede también ejecutarse de manera directa, sin consola interactiva. También puede usarse Julia en una jupyter-notebook (incluido Colab, aunque tiene sus vueltas). Y Julia cuenta también con su propio sistema de notebooks (Pluto) que es un poco distinto de jupyter. Por ahora nos concentraremos en la consola.

La consola nos muestra inicialmente un banner que nos da algo de información sobre la instalación y nos da un prompt:

  julia>

Allí podemos escribir código y ejecutarlo (apretando Enter). Para salir de la consola de Julia basta con correr la función:

  julia> exit()

Modos

Además del prompt para ejecutar código, la consola tiene otros tres modos que resultan muy útiles y la convierten en un entorno muy práctico para usar y gestionar Julia.

Primeros pasos

Para empezar a familiarizarse con la consola y con el lenguaje, corramos las siguientes líneas y observemos el resultado.

  julia> 1 + 2  
  julia> 2/2
  julia> x = 2
  julia> x^2
  julia> y = 3(2x+5); 
  julia> y 
  julia> x*y 
  julia> z = y/x
  julia> typeof(x)
  julia> typeof(z)
  julia> x<y
  julia> x>y
  julia> x==y
  julia> x!=y
  julia> w = x<y
  julia> typeof(w)
  julia> texto = "esta es una palabra";
  julia> println(texto[2])
  julia> println(texto[3:8])
  julia> println(texto[5:end])
  julia> typeof(texto)
  julia> texto[4] = "o" 
  julia> a = [1,2,3]
  julia> typeof(a)
  julia> length(a)
  julia> size(a)
  julia> a[2] = 8
  julia> a
  julia> a[3] = 9.5
  julia> b = [1,2.5]

Pasando en limpio

Hasta aquí todo muy sencillo, pero vale la pena remarcar algunos detalles.

Segundos pasos

Una de las preocupaciones de Julia es la expresividad. La idea es que la matemática se exprese en el código de la manera más sencilla posible. Un pequeño truquito para facilitar esa expresividad es la admisión de caracteres Unicode. Por ejemplo, en Julia podemos tener una variable llamada α. Para escribirla, basta tipear \alpha y luego presionar la tecla tab.

Probar las siguientes sentencias:

  julia> α = 2//3; β = 1//6;
  julia> α + β
  julia> typeof(α)  
  julia> c = 2+3im
  julia> abs(c) 
  julia> 2÷2
  julia> 7÷3
  julia> 7%3

(para obtener ÷, tipear \div+tab).

  julia> x = 1:10
  julia> typeof(x)
  julia> y = 3:2:1000
  julia> typeof(y)
  julia> length(y)
  julia> collect(x)
  julia> z = collect(y)
  julia> typeof(z) 
  julia> Base.summarysize(z)
  julia> Base.summarysize(y)
  julia> x = collect(x);
  julia> push!(x,2.3)
  julia> x
  julia> typeof(x)
  julia> valor = pop!(x)
  julia> valor
  julia> x
  julia> t= (3,4,7);
  julia> typeof(t)
  julia> t[2]
  julia> t[1:end-1]
  julia> t[1] = 5
  julia> D = divrem(13,5)
  julia> d,r = divrem(13,5)
  julia> d
  julia> r 
  julia> (dd,rr) = divrem(13,5)
  julia> dd
  julia> rr
  julia> r,d = d,r
  julia> print("d:",d,"r:",r)

Pasando en limpio

Nota: El ! en el nombre de la función no tiene valor sintáctico. Es sólo una (buena) convención de Julia. Las funciones cuyo nombre termina en ! modifican su argumento. Es bueno tenerlo en cuenta y respetar la convención cuando uno escribe sus propias funciones. Muchas funciones admiten variantes con y sin !. Por ejemplo: sort recibe un vector y devuelve una copia ordenada, preservando el original. En cambio sort! recibe el vector y lo altera de modo que quede ordenado.

Terceros pasos

Probemos un poco más de código:

  julia> z = begin 
              x = 2
              y = 3
              x*y
            end
  julia> z
  julia> w = (x=5;y=1;x+y)
  julia> w

begin ... end define un bloque de código. En Julia los bloques devuelven el valor de su última expresión. Por eso se asigna a z el valor de x*y. Lo mismo puede hacerse más compacto separando las expresiones con ;.

  julia> f(x) = 2x^2+1
  julia> z = f(2)
  julia> typeof(z)
  julia> w = f(1e-2)
  julia> typeof(w)
  julia> v = [1,2,3]
  julia> f(v)  

Definir funciones matemáticas en Julia es muy fácil. Se puede ver que el código es casi idéntico a lo que escribiríamos en papel. Al evaluar la función, Julia se encarga de manejar los tipos de dato adecuadamente.

Sin embargo, vemos que no se puede evaluar f en un vector. A diferencia de lo que ocurre en Python o Matlab, ninguna operación está definida por defecto para operar casillero a casillero. Es decir que en el ejemplo tenemos problemas por culpa de la operación ^ pero también por culpa de la suma: + . Como veremos en breve, esto es intencional, porque Julia tiene una sintaxis muy poderosa para hacer operaciones casillero a casillero.

Antes de investigar la evaluación casillero a casillero, intentemos graficar f. Para ello, usamos el paquete Plots que es el estándar para gráficos (hay otros). Para ello corremos:

  julia> using Plots
Nota: Hay dos comandos para importar paquetes. Uno es import, que es similar al import de Python. Si uno usa import es necesario usar el nombre del paquete como prefijo cada vez que se corre una función: Plots.plot(). El otro es using que trae todas las funciones y no requiere del uso del prefijo (podemos correr directamente plot()). En general en Julia se prefiere using. El principal riesgo de `using` es que como `using` trae todos los _nombres_ del paquete, podría ocurrir que uno quiera usar dos paquetes distintos que repiten el nombre de una función y generar un conflicto. Esta es la razón por la cual `Python` no tiene una sentencia de este tipo y prefiere `import`, que al obligar a usar el nombre del paquete como prefijo permite identificar cuál de las funciones con idéntico nombre se quiere correr. En `Julia` este es un problema muy improbable y por lo tanto se prefiere `using`. En el caso de haber algún conflicto, `Julia` mostrará un warning indicando exactamente qué funciones se pisan.

El primer dibujo simplón se puede hacer simplemente con:

  julia> plot(f)

Esto grafica en un dominio asumido por defecto. Si queremos un dominio diferente, necesitamos dar los valores de x sobre los que queremos evaluar la función:

  julia> plot(-1:0.1:1,f)

Notar que en general en otros lenguajes una función como plot() requiere dos secuencias de datos: una con valores de x y otra con valores de y. Aquí le estamos pasando un rango (una forma de vector, digamos) y una función.

También funciona en el formato usual. Para ello tendríamos que generar un vector de evaluaciones de f. Podríamos hacer esto con un for:

  julia> x = -1:0.1:1
  julia> y = zeros(length(x))
  julia> for i in 1:length(x)
             y[i] = f(x[i])
         end
  julia> plot(x,y)              

En Julia todos los bloques de código se cierran con end (como en Matlab). i in 1:length(x) indica que el índice i debe moverse dentro del rango de índices de x. Una alternativa piola es:

  julia> for i in eachindex(x)
  ...

La función eachindex() devuelve la secuencia de índices de x esto tiene varios sentidos:

También hay una alternativa más compacta:

  julia> y = [f(xx) for xx in x]
  julia> plot(x,y)              

Es decir: Julia admite definiciones por comprensión, como Python.

Broadcasting

Sin embargo, el mecanismo más natural para evaluar una función sobre un vector casillero a casillero no es ninguna de las anteriores, sino lo que se llama broadcasting:

  julia> y = f.(x)

El . indica que la función debe aplicarse lugar a lugar. Esto luce similar a Matlab, pero en realidad es considerablemente más poderoso, como iremos viendo a lo largo del curso. Por ahora observemos que en Matlab el . debe aplicarse sobre cada operación problemática: y = x.^2+x.^3. En Julia la sintaxis es general y se aplica a todo el lenguaje. En particular, en este caso lo podemos aplicar directamente a cualquier función:

  julia> g(x) = cos(x^2)-exp(x+1)
  julia> y = g.(x)

Ni el cuadrado, ni sumar uno, ni la exponencial ni el coseno son funciones admisibles sobre vectores. Sin embargo aplicamos el . sólo cuando evaluamos g. No se realiza cada operación por separado casillero a casillero, sino que directamente se evalúa g en cada lugar. La notación g.(x) es equivalente a broadcast(g,x). Volveremos sobre esto más adelante, porque esta sintaxis resulta sumamente poderosa. Por ahora, basta notar que el . nos permite aplicar cualquier función casillero a casillero.

Por último, ¿Cómo hacemos dos gráficos juntos?

  julia> plot(x,f.(x))
  julia> plot!(x,g.(x))

Hay dos versiones de plot: plot y plot!. La segunda modifica el gráfico existente y por lo tanto hace el segundo gráfico sobre el primero.

JIT

Una de las razones primordiales que hacen de Julia un lenguaje muy veloz es que utiliza un mecanismo denominado just in time compilation. La primera vez que se ejecuta una función, se la compila. Es decir: se traduce el código a lenguaje de máquina y se lo almacena en memoria. Las siguientes ejecuciones de la misma función serán muchísimo más veloces.

Pongamos esto a prueba. Cerremos la sesión (exit()) y abramos una nueva. Luego ejecutemos el siguiente código:

  julia> using Plots
  julia> x = 0:0.1:1; y = x.^2; z = cos.(x);
  julia> @time plot(x,y)  

@time nos permite calcular el tiempo que demora la ejecución de una función (y algunas cosas más). Se observa que el gráfico demora una fracción de segundo bastante perceptible, pero @time nos indica que la mayor parte de ese tiempo fue compilation time.

Corramos ahora:

  julia> @time plot(x,z)

Se observa que la ejecución es mucho más veloz y @time ya no reporta tiempo destinado a compilación.

Importante: La JIT compilation le permite a Julia alcanzar velocidades cercanas (¡o mayores!) a las de lenguajes compilados ahead of time como C o Fortran. Sin embargo, tiene el pequeño costo de que la primera ejecución de una función incluye el proceso de compilación. A esto se lo conoce como problema del primer plot justamente porque los plots suelen ser costosos y es una de las operaciones simples en las que el fenómeno resulta más notorio. Este problema puede eludirse haciendo compilación ahead of time (hay un paquete para eso). Los paquetes también tienen instrucciones de precompilación, es decir que cuando uno los importa usando `using` ya compilan algunas de sus funciones para tipos de datos usuales, acelerando el tiempo incluso de la primera corrida. Pero además cada nueva versión de Julia mejora el compilador y por lo tanto achica los tiempos de espera de las primeras corridas. Por último: veremos más adelante que uno puede hacer algunas cosas al programar para minimizar el impacto de este problema.


<< Volver al índice
>> Ir a la parte 2