Julia est un langage de programmation de haut niveau, performant et dynamique conçu pour le calcul scientifique et les applications de calcul numérique. Sa syntaxe est familière aux utilisateurs d'autres environnements de développement tels que MATLAB, R, Scilab, et Python. Julia est connue pour sa vitesse exceptionnelle et ses capacités avancées en termes de calcul parallèle et de gestion de données volumineuses.

Utilisation Bien qu'étant un langage généraliste dans sa conception, Julia est majoritairement utilisé dans le monde scientifique. On le retrouve notamment dans des domaines tels que la science des données, la modélisation numérique, la statistique, l'apprentissage automatique ou encore la biologie et la climatologie. Les utilisateurs du langage sont en majorité des ingénieurs, des chercheurs ou des étudiants, qui l'utilisent pour faire de la recherche scientifique ou comme un passe-temps. Certains utilisateurs trouvent dans Julia un langage avec une syntaxe simple comme Python tout en ayant des performances élevées.

Caractéristiques Julia combine les avantages des langages compilés (tels que C et Fortran) avec la flexibilité des langages dynamiques. Ses caractéristiques principales comprennent : Performance : Julia est conçue pour offrir des performances proches de celles du code natif compilé. Elle utilise un compilateur JIT (Just-In-Time) pour optimiser les performances à l'exécution. Syntaxe Similaire à MATLAB : Sa syntaxe est simple et similaire à celle de MATLAB, ce qui facilite la transition pour les utilisateurs de MATLAB et d'autres langages scientifiques. Typage Dynamique : Le langage utilise un système de types dynamique qui permet une grande flexibilité tout en offrant la possibilité de faire des optimisations à la compilation. Dispatch Multiple : Julia utilise le dispatch multiple pour la résolution de fonctions en fonction des types d'arguments, permettant une surcharge de fonction puissante et flexible. Interopérabilité : Julia peut interagir directement avec des bibliothèques C, Fortran, et Python, facilitant l'intégration avec des outils existants. Parallélisme et Concurrence : Le langage prend en charge le calcul parallèle et la gestion des tâches concurrentes, permettant des performances accrues pour des calculs intensifs.

Syntaxe Julia s'est inspiré des langages avec une syntaxe épurée tels que Python ou Ruby. Un code Julia est structuré en blocs d'expressions qui commencent par un comme function ou if et finit par end. Par convention, une indentation de 4 espaces est faite pour chaque nouveau bloc, mais elle n'est pas obligatoire. Les commentaires sont indiqués par le caractère croisillon (#). Julia propose de nombreux outils de syntaxe optionnels qui permettent de rendre le code plus concis.

Exemple de code en CExemple de code en Julia#include <stdio.h> // Pour printf #include <stdlib.h> // Pour la fonction abs

int max_valeur_absolue(int nombres[], int taille) { int max_abs = 0; for (int i = 0; i < taille; i++) { if (abs(nombres[i]) > max_abs) { max_abs = abs(nombres[i]);

return max_abs; }

int main() { int nombres[] = {3, -7, -1, 5}; int taille = sizeof(nombres) / sizeof(nombres);

printf("%d\n", max_valeur_absolue(nombres, taille)); // Affiche 7 return 0; }function max_valeur_absolue(nombres) max_abs = 0 for n in nombres if abs(n) > max_abs max_abs = abs(n) end end return max_abs end

# syntaxe concise max_valeur_absolue(nombres) = maximum(abs.(nombres))

println(max_valeur_absolue([3, -7, -1, 5])) # Affiche 7

Julia supporte le standard Unicode, ce qui peut être pratique pour des calculs scientifiques. Cependant pour son possible manque de lisibilité et de support par certains éditeurs, son utilisation est controversée. Par exemple, la fonction xi de Riemann peut être implémentée comme suit :

using SpecialFunctions: gamma as Γ, zeta as ζ

ξ(s) = 1/2 * s * (s-1) * π^(-s/2) * Γ(s/2) * ζ(s)

Performances Julia a été conçu pour avoir des performances élevées tout en étant un langage de haut niveau. Pour ce faire, il compile à la volée (JIT) le code Julia en code machine optimisé à l'aide de LLVM. La conception de Julia étant faite pour exploiter au mieux cette compilation à la volée, il en résulte des performances proches de celles de langages bas niveau (comme C ou Rust) et meilleures que d'autres langages JIT (comme Pypy ou ).

Julia étant dynamique, aucune erreur ne sera lancée si le compilateur n'a pas assez d'information pour optimiser efficacement le code, notamment sur le type des variables (type instability). Ce problème d'optimisation conduit à une baisse notable en performance, ce qui a pour effet de surprendre les nouveaux utilisateurs. Une page entière de la documentation officielle du langage est destinée à donner des techniques qui résolvent ce problème.

Polymorphisme Le modèle de Julia s'appuie principalement sur le polymorphisme ad-hoc (aussi appelé dispatch multiple ou multi méthodes) et le polymorphisme paramétré ou de type (aussi appelé programmation générique). Pour Julia, une fonction est un simple nom, qui peut posséder plusieurs méthodes. Une méthode est une implémentation spécifique de la fonction selon le nombre et le type de chaque paramètres de celle-ci. De cette manière, il est possible de créer une implémentation complètement différente pour chaque combinaison de types possibles. Par exemple :ma_fonction(x) = "x peut être de n'importe quel type" ma_fonction(x::Real) = "x est un nombre réel" ma_fonction(x::Integer) = "x est un entier de n'importe quelle taille" ma_fonction(x::UInt8) = "x est un entier non signé de 8 bits"

ma_fonction(x, y) = "x et y sont de n'importe quel type" ma_fonction(x::T, y::T) where {T} = "x et y sont du meme type qui est $T"

println(ma_fonction("bonjour")) # x peut être de n'importe quel type println(ma_fonction(π)) # x est un nombre réel println(ma_fonction(3)) # x est un entier de n'importe quelle taille println(ma_fonction(0x01)) # x est un entier non signé de 8 bits

println(ma_fonction(3.14, 1)) # x et y sont de n'importe quel type println(ma_fonction(3.14, 1.0)) # x et y sont du meme type qui est Float64 Un autre exemple plus classique est celui de l'addition. Pour Julia, la simple opération 1+2 est traduite en +(1, 2), ou +est la fonction et +(::Int, ::Int) la méthode, spécifique et optimisée pour des nombres entiers. Il existe alors une +(::Number, ::Number) ou même +(::Real, ::Complex). La liste de toutes les méthodes pour une fonction donnée peut s'obtenir avec methods(nom_de_la_fonction).

Les variables n'ont généralement pas besoin d'être typées grâce à la compilation à la volée, les types étant souvent inférés lors de la compilation. Si ce n'est pas le cas, Julia utilise un pour garder un typage dynamique. C'est ce dispatch dynamique qui différencie le dispatch multiple de la surcharge de fonction (comme en C++ ou en Java).

Cette implémentation du polymorphisme en Julia se rapproche de celles des langages Common Lisp, Dylan ou Fortress. Programmation objet En Julia, tout est objet. Cependant, Julia ne possède pas de classe comme la vision classique de la programmation orientée objet. À la place, la création de nouveaux objets avec le struct et de nouveaux objets abstraits avec abstract type combiné au dispatch multiple suffit. Par exemple : abstract type ObjetSpatial end

function collision(x::ObjetSpatial, y::ObjetSpatial) if taille(x) > 100 && taille(y) > 100 return "Explosion !" else return collision_avec(x, y) end end

struct Asteroide <: ObjetSpatial rayon::Float64 end taille(a::Asteroide) = 2 * a.rayon

struct Vaisseau <: ObjetSpatial taille::Float64 end taille(v::Vaisseau) = v.taille

collision_avec(a::Asteroide, v::Vaisseau) = "a/v" collision_avec(v::Vaisseau, a::Asteroide) = "v/a" collision_avec(a::Asteroide, a2::Asteroide) = "a/a" collision_avec(v::Vaisseau, v2::Vaisseau) = "v/v"

println(collision(Asteroide(101), Vaisseau(300))) # "Explosion !" println(collision(Asteroide(101), Vaisseau(50))) # "a/v" println(collision(Asteroide(10), Asteroide(10))) # "a/a" Julia permet également de rendre le concept de très naturel. Par exemple, pour rajouter un nouvel objet Planete à l'exemple précédent :struct Planete <: ObjetSpatial rayon::Float64 end taille(p::Planete) = 2 * p.rayon

collision_avec(p::Planete, a::Asteroide) = "p/a" collision_avec(a::Asteroide, p::Planete) = "a/p" collision_avec(p::Planete, v::Vaisseau) = "p/v" collision_avec(v::Vaisseau, p::Planete) = "v/p"

println(collision(Planete(1_000), Vaisseau(30))) # "p/v"

Avec le concept de classe, il aurait fallu écrire la méthode collision_avec(a::Asteroide, p::Planete) à l'intérieur de la classe Asteroide. En Julia, il suffit simplement d'écrire des nouvelles méthodes.

Cette programmation objet particulière se basant sur le dispatch multiple est similaire à celle du langage Common Lisp (Common Lisp Object System). Cependant, Julia ne possède pas de concept d'héritage des objets concrets. En général, la composition combinée à la programmation générique ne nécessite pas un besoin d'héritage. La documentation officielle précise (traduit de l'anglais) : Il s’avère que pouvoir hériter du comportement est beaucoup plus important que pouvoir hériter de la structure, et hériter des deux entraîne des difficultés importantes dans les langages orientés objet traditionnels.Pour rendre le code plus robuste, des patrons de conception spécifiques à la conception de Julia qui ne demandent pas l'utilisation de classes sont couramment utilisés et documentés.

Reprenant les concepts de la programmation fonctionnelle, les objets sont immuables par défaut. Pour rendre un objet mutable, il faut le préciser avec le mot-clé mutable struct à la place de struct.

Programmation fonctionnelle Julia supporte la création de fonctions d'ordre supérieur (des fonctions qui prennent en paramètres d'autres fonctions) ainsi que de fonctions anonymes. De ce fait, des outils venant de la programmation fonctionnelle sont disponibles :

Des fonctions d'ordre supérieur comme ou . Des opérateurs comme l'opérateur pipe qui permet de transformer fonction1(fonction2(x)) en x |> fonction2 |> fonction1 ou l'opérateur ∘ (\circ) pour la composition de fonctions. La syntaxe par liste en compréhension, très similaire à celle en Python.

Vectorisation Comme la plupart des langages ayant une portée scientifique, Julia fournit une implémentation très complète de tableaux à une ou plusieurs dimensions. Ces tableaux peuvent représenter des vecteurs (une dimension) ou encore des matrices (deux dimensions). Ils permettent également de vectoriser le code (c'est-à-dire effectuer une même opération sur tout un ensemble d'éléments) à l'aide de la dot syntax (littéralement syntaxe à point). Par exemple:julia> X = [1 2 3; 4 5 6; 7 8 9] 3×3 Matrix{Int64}: 1 2 3 4 5 6 7 8 9

julia> f(x) = 2x + 4 f (generic function with 1 method)

julia> f.(X) # on calcule f(x) sur chaque élement x de X 3×3 Matrix{Int64}: 6 8 10 12 14 16 18 20 22Cependant et contrairement au langages scientifiques les plus connus comme Python, R ou MATLAB, cette vectorisation n'améliore pas les performances puisque toutes les fonctions et même les tableaux eux mêmes sont écrits en Julia. En Julia, écrire des boucles à la main pour effectuer les opérations une à une est déjà très performant. Par exemple, la fonction suivante ne sera pas moins performante que f.(X):function f_vectorise(X) Y = similar(X) for i in eachindex(X) Y[i] = f(X[i]) end return Y endLa vectorisation permet donc simplement de rendre le code plus concis et lisible pour les utilisateurs.

Réflexivité et métaprogrammation