Julia nació para resolver el problema de los dos lenguajes: escribir el prototipo en Python o MATLAB y luego reescribir lo lento en C o Fortran. Su propuesta es que un lenguaje puede ser cómodo y rápido a la vez, y lo consigue con una idea poco frecuente: despacho múltiple compilado especializando por tipos en ejecución.
🎯 Por qué está en este programa
Julia es un primo de la familia array / científica (Atlas), junto a APL, J, R, MATLAB y Fortran.
Aporta al programa el despacho múltiple como paradigma central (clase 111) — la idea que Common Lisp tenía con CLOS, aquí convertida en la forma normal de organizar el código. Y aporta el ejemplo más claro de JIT especializante (clase 126).
| Año | 2012; 1.0 en 2018, con promesa de estabilidad; 1.11 actual |
| Autoría | Jeff Bezanson, Stefan Karpinski, Viral Shah, Alan Edelman — MIT |
| Familia | Array / científica; con Lisp, Python, MATLAB y R dentro |
| Paradigma | Multiparadigma con despacho múltiple; funcional y por arreglos |
| Tipado | Dinámico con tipos ricos, usados para especializar en compilación |
| Memoria | Recolección de basura |
| Ejecución | JIT sobre LLVM, compilando una versión por combinación de tipos |
| Estado | 🟢 En crecimiento en cálculo científico, optimización y simulación |
En 2012, cuatro investigadores del MIT publicaron un manifiesto —Why We Created Julia— que enumeraba lo que querían, y que se lee como una lista de deseos imposible:
Queremos la velocidad de C, el dinamismo de Ruby, la homoiconicidad de Lisp con macros de verdad, la notación matemática de MATLAB, la potencia estadística de R, la facilidad de Python para guiones, la capacidad de Perl para procesar texto y la potencia lineal de Matlab. Y que sea fácil de aprender.
El problema concreto que atacaban tiene nombre en la comunidad científica: el problema de los dos lenguajes. Se prototipa en un lenguaje cómodo y, cuando hace falta rendimiento, se reescriben las partes críticas en C o Fortran (clase 155) — con el coste de mantener dos versiones y la frontera entre ellas.
La solución de Julia es el despacho múltiple con especialización en compilación, y es lo que hace que funcione: el JIT compila una versión de cada función para cada combinación concreta de tipos con la que se llama, así que el código genérico acaba siendo tan rápido como el escrito a mano para esos tipos.
Julia 1.0 (2018) estabilizó el lenguaje, y desde entonces la evolución se ha centrado en el arranque —su punto débil histórico— y en las herramientas.
Es el concepto central y merece verlo con calma (clase 111):
area(c::Circulo) = π * c.r^2
area(r::Rectangulo) = r.a * r.b
# Y con DOS tipos a la vez:
interactuar(a::Asteroide, n::Nave) = "la nave esquiva"
interactuar(a::Asteroide, p::Planeta) = "impacto"
interactuar(n::Nave, p::Planeta) = "aterrizaje"
El método se elige según los tipos de TODOS los argumentos, no solo del primero. En un lenguaje con despacho simple —Java, C++, Python— eso obliga al patrón Visitante o al doble despacho (clase 151); aquí es la forma normal de escribir.
Y la consecuencia arquitectónica es grande, y explica el ecosistema de Julia:
Un paquete define el tipo `Cuaternion`.
OTRO paquete, sin conocerlo, define la función `graficar`.
Y un TERCERO puede escribir `graficar(::Cuaternion)` sin tocar ninguno de los dos.
Eso hace que las bibliotecas se combinen sin haberlo previsto —lo que la comunidad llama composabilidad— y es la razón de que el ecosistema científico de Julia sea tan interoperable.
Y el JIT especializante es la otra mitad:
f(x, y) = x * y + 1
f(2, 3) # compila una versión para (Int64, Int64)
f(2.0, 3.0) # compila OTRA para (Float64, Float64)
@code_native f(2, 3) # ← se puede VER el código máquina generado
El mismo código genérico produce código máquina especializado, sin comprobaciones de tipo en el bucle interno. Eso es lo que iguala el rendimiento con C.
Y el coste, que hay que decir (clase 164): el tiempo hasta el primer gráfico. Como se compila al llamar, la primera ejecución de cada función paga la compilación — lo que hacía frustrante el arranque interactivo. Julia 1.9+ lo ha mejorado mucho con la caché de código nativo en los paquetes, y sigue siendo su punto más criticado.
PackageCompiler.jl y las imágenes de sistema: binarios autocontenidos (clase 174).Threads.@threads, tareas) y computación distribuida en la biblioteca
estándar (clase 135).ccall con C sin escribir envoltorios (clase 156), y
PythonCall, RCall, MATLAB.jl para llamar a los vecinos.Pkg, el gestor de paquetes, con entornos por proyecto y Manifest.toml como fichero de
bloqueo (clase 143) — de lo mejor diseñado de esta lista.julia main.jl < entrada.txt # el comando
julia --project=. -e 'using Pkg; Pkg.test()' # pruebas con el entorno del proyecto
julia # el REPL, que es donde se trabaja de verdad
# ] activate . ] instantiate ] test ← el modo de paquetes
Esta versión se escribe aquí y no está verificada en CI (clase 040).
v = parse.(Float64, split(readline()))
total = v[1] * v[2] * (1 - v[3])
println("Total: ", round(total; digits=2))
Lo que hay que ver.
parse.(Float64, ...) con el punto es la difusión (broadcasting): el punto aplica la
función a cada elemento. Es la marca de la casa, y funciona con cualquier función y cualquier
operador: a .+ b, sin.(v), f.(x, y). Es map convertido en sintaxis uniforme (clase 115),
y elimina la mayoría de los bucles.v es un Vector{Float64} con tipo concreto, y el compilador lo sabe: por eso la aritmética de
la segunda línea genera el mismo código máquina que en C.round(total; digits=2) usa un argumento con nombre tras el punto y coma — otra decisión de
legibilidad de la familia científica.⏮️ Volver al Atlas · 🗂️ Todas las fichas · 🔗 Relacionadas: Python · R · MATLAB · Fortran · Common Lisp