Julia, une introduction, mais vite

Julia est un langage de programmation récent orienté (très haute) performance : il s'agit de l'un des très rares langages à être utilisé sur un superordinateur ! Par exemple, il a été utilisé pour le projet Celeste, qui consiste en l'analyse d'images astronomiques : les chercheurs ont pu cataloguer 188 millions d'objets astronomiques, dénichés dans pas moi de 178 téraoctets de données, en moins de quinze minutes — le fruit de trois années de recherche.

Julia a ainsi été préféré au C, au C++ ou au Fortran, les trois langages de prédilection pour ce genre d'application. En effet, ces langages ont l'avantage de limiter leur impact sur la performance des applications : on a accès très directement à la machine, sans mécanisme lourd comme une machine virtuelle. De plus, les compilateurs optimisent très fortement le code généré, pour qu'il tourne plus vite. Julia dispose des mêmes avantages (donc en performance, point crucial pour cette application), tout en étant plus facile à utiliser : il s'agit d'un langage bien plus dynamique (comme Python ou Ruby), avec des notations mathématiques habituelles (il est similaire à MATLAB, Octave ou encore Fortran).

1 commentaire Donner une note  l'article (5)

Article lu   fois.

L'auteur

Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

1. Premières lignes

La première chose à faire pour tester Julia, c'est probablement de l'installer. Ceux qui préfèrent s'économiser cette peine peuvent toutefois l'utiliser en ligne : JuliaBox propose une interface Web basé sur Jupyter (ex-IPython), entièrement gratuite.

Ensuite, testons quelques commandes ! Julia peut-il effectuer des calculs basiques ? Bien évidemment :

 
Sélectionnez
julia> 1 + 1
2
Image non disponible
Exécution d'une première ligne de code avec Julia : 1 + 1

Jusque-là, rien de très étonnant. On peut aussi afficher un peu de texte à l'écran en utilisant la fonction println :

 
Sélectionnez
julia> println("Hello, World!")
Hello, World!

Quelques remarques. Pour appeler une fonction, on utilise son nom, puis des parenthèses : on insère entre celles-ci les arguments à passer à la fonction. Rien de très étonnant, c'est comme cela que les choses se passent dans la majorité des langages de programmation. Les chaînes de caractères s'indiquent exclusivement entre guillemets doubles : ". Les guillemets simples, ', servent quant à eux à indiquer un caractère simple : Julia est très précis sur les types et fait la distinction entre un caractère (comme 'a') et une chaîne d'un seul caractère ("a"). La fonction typeof donne le type de son argument :

 
Sélectionnez
julia> typeof("a")
String

julia> typeof('a')
Char

En tant que langage d'abord prévu pour les applications mathématiques, on s'attend à ce que le système de types soit assez robuste : une chaîne de caractères contenant un nombre ne devrait pas être confondue avec ce nombre. Une opération d'addition entre la chaîne de caractères "42.0" et le nombre à virgule flottante 1.0 n'est ainsi pas permise :

 
Sélectionnez
julia> "42.0" + 1.0
ERROR: MethodError: no method matching +(::String, ::Float64)
Closest candidates are:
  +(::Any, ::Any, ::Any, ::Any...) at operators.jl:502
  +(::Bool, ::T<:AbstractFloat) where T<:AbstractFloat at bool.jl:112
  +(::Float64, ::Float64) at float.jl:395
  ...
Stacktrace:
 [1] top-level scope at none:0

Vous remarquerez au passage la longueur du message d'erreur : autant que possible, Julia vous donne toutes les informations nécessaires en cas d'erreur. Cela n'a pas toujours été le cas avec les versions préliminaires, mais les développeurs de Julia y prêtent une certaine attention depuis la version 1.0.

Ce message est assez courant : il indique qu'il n'existe pas de méthode dont le nom est + et qui prend, en argument, une chaîne de caractères (String) et un nombre à virgule flottante (Float64). Par contre, l'interpréteur connaît plusieurs méthodes portant ce nom, mais avec d'autres arguments.

On peut voir ici quelques spécificités de Julia. Le langage fait la distinction très nette entre les fonctions et les méthodes : une fonction n'est qu'un nom pour une opération (comme + ou println), tandis qu'une méthode est une implémentation de cette fonction. À l'exécution, Julia choisit la méthode à appeler en fonction du type de chacun des arguments. Par exemple, si on tente d'additionner un nombre à virgule flottante (Float64) avec un nombre entier (Integer), Julia regarde les méthodes disponibles pour la fonction + : on peut additionner directement un Float64 avec un Integer, mais il existe des méthodes plus spécifiques au niveau des types, selon qu'on a un Int64 (entier signé sur 64 bits), UInt64 (entier non signé sur 64 bits), etc. Le moteur d'exécution choisira toujours la méthode la plus précise, au moment où la fonction doit être appelée. Pour faire du polymorphisme à l'exécution, contrairement aux langages statiquement typés, les arguments ne sont pas obligés d'avoir un type statique à partir duquel le langage détermine quelle implémentation choisir à l'exécution(1).

 
Sélectionnez
julia> methods(+)
# 163 methods for generic function "+":
[1] +(x::Bool, z::Complex{Bool}) in Base at complex.jl:277
[2] +(x::Bool, y::Bool) in Base at bool.jl:104
[3] +(x::Bool) in Base at bool.jl:101
[4] +(x::Bool, y::T) where T<:AbstractFloat in Base at bool.jl:112
[5] +(x::Bool, z::Complex) in Base at complex.jl:284
[6] +(a::Float16, b::Float16) in Base at float.jl:392
[7] +(x::Float32, y::Float32) in Base at float.jl:394
[8] +(x::Float64, y::Float64) in Base at float.jl:395
[9] +(z::Complex{Bool}, x::Bool) in Base at complex.jl:278
[10] +(z::Complex{Bool}, x::Real) in Base at complex.jl:292

2. Installation de Juno

Utiliser Julia uniquement en ligne de commande n'est pas le plus pratique, même si l'interpréteur dispose d'un grand nombre de fonctionnalités. Bon nombre d'utilisateurs préfèrent un environnement de développement. L'écosystème Julia en dispose de deux : Juno, basé sur Atom (l'EDI développé par GitHub) est le plus développé (avec une interface qui rappelle RStudio ou MATLAB : on dispose d'une zone de code, d'un accès direct à l'interpréteur, d'une zone pour visualiser des graphiques, la documentation ou encore les variables définies dans l'interpréteur) ; Visual Studio Code est un autre environnement de développement, cette fois-ci proposé par Microsoft, par défaut un peu plus rustre. Si vous préférez, il existe des extensions pour d'autres environnements : IDEA (IntelliJ, PyCharm, CLion, etc.), Sublime Text. Les éditeurs de texte en ligne de commande ne sont pas non plus en reste : tant Vim qu'Emacs disposent d'extensions.

Image non disponible
Interface de Juno
Image non disponible
Interface de Visual Studio Code

Pour ce tutoriel, on utilisera Juno, même s'il est un peu plus compliqué à installer que Visual Studio Code. Pour ceux qui préféreraient néanmoins ce dernier, il suffit d'installer l'environnement, puis l'extension Julia.

L'installation de Juno se passe en deux étapes. Il faut d'abord télécharger Atom sur le site officiel :

Image non disponible
Site officiel d'Atom

Une fois l'installateur téléchargé, lancez-le. Celui-ci installe Atom pour l'utilisateur courant, c'est-à-dire qu'il n'y a pas besoin de droits d'administrateur sur la machine.

Image non disponible
Installation d'Atom en cours

Au premier lancement, Atom propose une série d'actions à réaliser :

Image non disponible
Premier lancement d'Atom

Sélectionnez l'option d'installation de paquets, puis cliquez sur Open Installer pour lancer l'installateur de paquets.

Image non disponible
Fenêtre intermédiaire avant le lancement de l'installation des paquets de Juno

Dans la colonne de gauche, cherchez uber-juno :

Image non disponible
Installation du paquet uber-juno

Vous pourrez revenir, par la suite, à cet écran en utilisant le raccourci Ctrl+,, puis en sélectionnant l'option Install (ce qui est ici déjà fait).

Cliquez sur le bouton Install et attendez que l'installation se passe.

Image non disponible
Installation de Juno : les fichiers nécessaires sont en cours de téléchargement
Image non disponible
Installation de Juno : les paquets sont en cours d'installation

À la fin du processus, une petite fenêtre vous demande si vous souhaitez utiliser l'interface adaptée pour Julia. Si vous refusez, vous retrouverez l'interface habituelle d'Atom, notamment sans accès direct à l'interpréteur Julia.

Image non disponible
Installation de Juno : choix des panneaux à afficher

Et voilà, Juno est disponible et prêt à l'utilisation !

Image non disponible
Interface par défaut de Juno

Si vous souhaitez installer un autre thème que le sombre affiché par défaut, utilisez le raccourci Ctrl+,, sélectionnez l'entrée Themes.

Image non disponible
Choix d'un thème

Choisissez le thème que vous préférez pour l'interface (UI Theme) et la syntaxe du code (Syntax Theme). Par exemple, en choisissant deux fois One Light (la version claire du thème par défaut), vous obtiendrez ce rendu :

Image non disponible
Juno avec le thème One LightGest

3. Gestion des paquets

L'une des forces de Julia, c'est son écosystème très dynamique : en permanence, on voit de nouveaux paquets apparaître. JuliaObserver offre un moteur de recherche dans ces paquets, ainsi qu'une catégorisation et des tendances. Il est très facile d'y faire entrer un paquet : il suffit de l'enregistrer dans le dépôt JuliaRegistries. N'importe qui ne peut donc pas ajouter un paquet, tout ajout est vérifié avant d'être disponible au grand public.

Selon vos intérêts, différents paquets pourraient vous intéresser. La majorité est orientée vers le calcul scientifique, au sens large, mais on peut utiliser Julia dans n'importe quelle application :

  • pour le développement Web, la communauté JuliaWeb fournit un bon nombre de fonctionnalités de base ; Genie s'impose comme framework de développement d'applications ;

  • pour la visualisation de données, Plots est la référence ;

  • pour les interfaces graphiques, Blink offre la possibilité d'utiliser les technologies du Web ;

  • pour travailler avec des graphes, LightGraphs est une bibliothèque très développée, avec un bon nombre d'extensions pour les besoins plus spécifiques ;

  • pour le traitemente d'images, JuliaImages fournit un bon nombre de paquets intéressants ;

  • pour l'apprentissage automatique et la science des données en général, DataFrames fournit une structure de données tabulaire qui facilite les requêtes et autres traitements ; Query fournit un langage de type SQL pour effectuer des requêtes sur un grand nombre de structures de données ; Flux offre des réseaux neuronaux très performants ; ScikitLearn offre d'autres algorithmes d'apprentissage ;

  • pour l'optimisation mathématique, JuMP est de plus en plus reconnu comme meneur (et pas qu'en Julia) ;

  • pour les équations différentielles, JuliaDiffEq est une interface de programmation et regroupe les solveurs les plus performants actuellement (implémentés en Julia pour la plupart).

Il reste une question à se poser : comment installer un paquet ? Le gestionnaire de paquets s'appelle en appuyant sur ]. De là, la commande add sert à ajouter des paquets et rm à en désinstaller. Le gestionnaire de paquets gère bien évidemment toutes les dépendances entre paquets.

Une fois installé, vous pouvez importer le contenu d'un paquet avec l'instruction using. Par exemple, pour Plots, utilisez using Plots : toutes les fonctions définies par le paquet, comme plot(), seront alors disponibles.

Image non disponible
Installation d'un paquet

4. Éléments de syntaxe

Julia utilise un syntaxe sans indentation (à la Python) ni accolades (à la C et famille) : un bloc commence avec un mot clé (if, while, begin, etc.) et se termine par end. Par exemple, pour boucler sur une série de nombres et les afficher :

 
Sélectionnez
for i in 1:10
    println(i)
end

Les boucles for ne fonctionnent pas comme dans la majorité des langages : Julia préfère itérer sur un conteneur qu'incrémenter manuellement une variable ; il n'existe pas de forme avec trois arguments (initialisation, condition, mise à jour, comme en C). Ici, la boucle itère sur une plage de nombres, entre 1 et 10, avec un incrément d'une unité. On pourrait aussi itérer sur les éléments d'un tableau :

 
Sélectionnez
for i in [1, 3, 4, 9, 10]
    println(i)
end

Les conditions s'écrivent avec les mots clés if, elseif et else :

 
Sélectionnez
if x >= 0elseif x <= 1elseend

En tant que langage orienté mathématiques, Julia permet d'utiliser des comparaisons multiples : pour vérifier que x est dans l'intervalle [0, 1], on peut utiliser la condition 0 <= x <= 1. La majorité des autres langages impose de séparer la condition en deux parties : x >=0 && x <= 1.

 
Sélectionnez
if 0 <= x <= 1end

La syntaxe des boucles while est très similaire aux conditions, avec une condition :

 
Sélectionnez
while trueend

L'étape d'après est d'organiser son code en fonctions, qui se déclarent avec le mot clé function, toujours suivi de parenthèses. Si la fonction prend un ou plusieurs arguments, ils sont indiqués entre les parenthèses :

 
Sélectionnez
function zero()
    return 0
end

function sum(a, b)
    return a + b
end

La notation :: indique le type d'un argument :

 
Sélectionnez
function sum(a::Int, b::Int)
    return a + b
end

Les structures de données immuables sont déclarées avec le mot clé struct :

 
Sélectionnez
struct Point
    x::Float64
    y::Float64
end

Instancier une structure de données se fait comme un appel de fonction :

 
Sélectionnez
p = Point(0.0, 1.0)

Cependant, on ne peut pas modifier les instances de ces objets : pour p, on ne peut que lire les valeurs des champs x et y, pas y écrire !

 
Sélectionnez
println(p.x) # Affiche "0.0"
p.x = 1.0 # Erreur : "ERROR: setfield! immutable struct of type Point cannot be changed"

En effet, de base, Julia oriente les utilisateurs vers un style de programmation plutôt fonctionnel : si les valeurs des structures de données ne peuvent pas changer, les sources de problème dans le code sont fortement réduites.

Pour outrepasser cette limitation, il faut déclarer la structure de données comme mutable struct :

 
Sélectionnez
mutable struct Point
    x::Float64
    y::Float64
end

p = Point(0.0, 1.0)
println(p.x) # Affiche "0.0"
p.x = 1.0 # Plus d'erreur
println(p.x) # Affiche "1.0"

5. Travailler avec Julia

On peut utiliser Julia de plusieurs manières, le langage et son environnement sont assez flexibles. La manière la plus basique de l'utiliser est en console, avec l'interface REPL (read–eval–print loop) : on tape des commandes, on récupère des résultats.

Quand on commence à avoir un peu plus de code, on finit rapidement par le mettre dans des fichiers, avec des fonctions, des structures, un petit script pour lancer le tout. Pour inclure un fichier (que ce soit depuis un fichier Julia ou l'interface REPL), on utilise la fonction include, qui exécute directement tout le code du fichier inclus :

  • depuis un fichier, on peut inclure facilement les fichiers dans le même dossier : include("autrefichier.jl") ;

  • depuis l'interface REPL, il vaut mieux inclure le chemin complet vers le fichier à exécuter : include("/usr/Thibaut/fichier.jl") ou include("C:\\Users\\Thibaut\\fichier.jl").

Cependant, cette manière de travailler n'est pas toujours pratique. Pour l'analyse de données, par exemple, une interface de type Jupyter permet de mélanger du code et des résultats (notamment des graphiques) dans un format de calepin : IJulia.jl.

Image non disponible
Interface de IJulia

Pour ceux dont le métier de base est le développement informatique, l'interface REPL n'est pas tellement rebutante. Par contre, en développant une application, on en vient parfois à ajouter, enlenver des fonctions. En incluant toujours un script (ou en important un module en cours de développement), toutes les anciennes versions des fonctions restent disponibles, à moins d'être écrasées : si on supprime une fonction de son code, elle restera disponible depuis le REPL. Du code risque donc de continuer de fonctionner, alors qu'il repose sur des fonctions qui n'existent plus… La seule manière de bien tester son code est alors de relancer Julia. Revise.jl est prévu pour résoudre ce problème : il recharge automatiquement le code modifié et supprime les fonctions qui n'existent plus. Son utilisation est très simple : après installation, il suffit d'importer le module (import Revise) ; tout code importé par la suite est alors suivi de près par Revise et rechargé si besoin. La grande limitation actuelle de Revise.jl est que le paquet ne peut rien faire quand une structure de données change : dans ce cas, on aura droit à une erreur ERROR: invalid redefinition of constant Point, la seule solution est de relancer une session Julia. (Bien évidemment, Revise.jl peut s'utiliser en parallèle avec IJulia.)

Les scientifiques pourraient profiter de DrWatson.jl, qui cherche à automatiser le chargement et la sauvegarde de données d'expériences numériques, notamment avec de grandes variations dans les paramètres des expériences. DrWatson.jl enregistre les valeurs des paramètres, gère les noms de fichiers automatiquement.

6. Et après ?

Bien évidemment, cette petite introduction ne montre pas tout ce que Julia peut faire ! Pour mieux organiser son code, on peut utiliser des modules, prévus pour être faciles à partager avec d'autres. Un module consiste en une série de fonctions et de structures de données, certaines étant exportées (accessibles au monde extérieur) et d'autres pas.

Julia dispose d'une série de fonctionnalités pour l'analyse de performance : principalement, @time pour mesurer le temps d'exécution d'une commande et la mémoire consommée, @profile pour montrer le temps que prend chaque partie d'une fonction.

En outre, regardez la grande quantité de modules d'extension disponibles (ainsi que la bibliothèque standard) : il est plus que probable que toutes les fonctions de base dont vous auriez besoin pour votre application soient d'ores et déjà disponibles !

Si vous souhaitez des introductions plus longues à Julia, regardez par exemple Julia Express ou ThinkJulia.jl (aussi disponible au format papier).

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   


En C++, par exemple, on peut utiliser std::variant et std::visit pour obtenir le même résultat, avec bien plus de lourdeur.

  

Copyright © 2019 Thibaut Cuvelier. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.