author | tags |
---|---|
emchateau |
julia |
We are greedy: we want more. We want a language that’s open source, with a liberal license. We want the speed of C with the dynamism of Ruby. We want a language that’s homoiconic, with true macros like Lisp, but with obvious, familiar mathematical notation like Matlab. We want something as usable for general programming as Python, as easy for statistics as R, as natural for string processing as Perl, as powerful for linear algebra as Matlab, as good at gluing programs together as the shell. Something that is dirt simple to learn, yet keeps the most serious hackers happy. We want it interactive and we want it compiled.
Bezanson, Jeff, Stefan Karpinski, et Edelman Viral B. Shah Alan. 2012. « Why We Created Julia ». férvier 2012. https://julialang.org/blog/2012/02/why-we-created-julia/.
- Documentation du langage
- Noteworthy Differences from other Languages
- The Fast Track to Julia (aide-mémoire)
- Julia est composable
- Julia est concis
- Julia est performant
- Julia est productif
- Julia est facile à maintenir
- Julia est libre et open-source
Outre ces qualités, il nous semble que la consistance et la clareté de sa syntaxe en fait un très bon candidat pour l’apprentissage de la programmation.
- langage dynamiquement typé, mais avec des types optionnels
- built-in types équivalent à des types définis par les utilisateurs
- compilation JIT
- utilisation de multiple dynamic dispatch
- capacités de métaprogramming
- appel natif de librairies en C et Fortan
- 0.0 – 2009
- 0.1 – Feb 14, 2012
- 1.0 – Aug 8, 2018 (LTS)
- 1.6 – Mar 24, 2021 (LTS)
- 1.9 – May 07, 2023
- 1.10 – December 27, 2023 cf. https://julialang.org/blog/2023/12/julia-1.10-highlights/
- facile à apprendre depuis Python
- les librairies Python sont toutes utilisables vi PyCall.jl ou PythonCall.jl
- vitesse d’exécution comparable à celle de C
- parallélisation fait partie du langage
cf. https://youtu.be/qhrY0c_BHs8?si=fyPDO-f0U-PXVoJs
Télécharger Julia depuis la page dédiée et suivre les instructions https://julialang.org/downloads/
Plusieurs IDE sont disponible, le plus courramment utilisé consiste en l’utilisation de l’extension Julia de Visual Studio Code.
Il existe un Kernel Julia pour les notebooks Jupyter mais aussi un environnement de notebooks interactifs nativement développé en Julia Pluto.jl
Démarrer Julia
julia
Arrêter Julia
exit()
ou
<Ctl-D>
Premiers pas en ligne de commande ou REPL (Read-Eval-Print-Loop)
println("Hello world !")
1 + 2
sqrt(4)
Accéder à de l’aide
?
println
Enter
ou Backspace
pour quitter l’aide.
Shell
;
pwd
Gestionnaire de paquets
]
Backspace
pour quitter le gestionnaire de paquets.
Il n’y a pas d’indentation en Julia. Pour définir un bloc d’instruction, on commence par un mot-clef et ce bloc se termine par un end
if a >= b
end
Pour concaténer une chaîne de caractère on utilise *
. Le symbole puissance ^
permet de répéter un caractère. On peut représenter un caractère explicitement avec des apostrophes simples ''
et des chaînes de caractères avec ""
'a'^5 * "bc"
C’est souvent l’utilisation des types qui permet à Julia une exécution très rapide. Il est possible de créer une liste non typée ou des listes typées. Pour ajouter un type à tous les éléments de la liste, on le précise avant la liste.
slow = []
fast = Int[]
Dans la syntaxe de Julia le premier élément est 1
contrairement à d’autres langages où l’index est 0
. On peut accéder avec le mot-clef end
au dernier élément. La dernière valeur est incluse dans la plage.
list[1] == first(list)
list[end] == last(list)
list[begin+1:end-1]
Julia ne dispose pas de classe à proprement parler. Toutefois, il est possible de créer une structure de données avec struct
. Pour créer une structure mutable, on précède par le mot-clef mutable
. Cette distinction est permise pour permettre l’accélération du code.
struct X
field
end
mutable struct X
field::Float64
end
Julia propose une compilation à la volée JIT. Tout le code est compilé avant d’être exécuté lors du chargement des modules.
Multiméthode, le code à exécuter pour une fonction dépend des types de tous les arguments.
- fonction : un nom (
+
,read
), souvent partagé entre des paquets (filter!
) - méthode : une implémentation d’une fonction selon les types
+(::Int, ::Int)
- polymorphisme à la C++, en Python seul le type de l’objet est considéré
println("Vive Julia !")
Les types sont importants en Julia. Attention on ne peut concaténer que des chaînes
println("Vive Julia " * VERSION * " !")
# La constante VERSION a pour type VersionNumber.
println("Vive Julia " * string(VERSION) * " !")
Lors de la définition d’une variable, l’interpréteur lui assigne un type
a = 42
println(typeof(a))
b = 3.14159
println(typeof(b))
c = 'c'
println(typeof(c))
d = "Julia"
println(typeof(d))
Le type d’une variable peut changer au cours de sa vie
a = 42
println(typeof(a))
a = 'c'
println(typeof(a))
Pour le débogage, @show affiche aussi l’expression :
@show a;
Commentaires
# commentaire simple
#=
Commentaire
Multi-ligne
=#
L’interpolation des chaînes de caractère est utile
langage = "Julia"
println("Vive $langage $(Version) !")
Unicode est géré nativement, même pour les noms de variables.
γs_regex = r"γ|Γ" # \gamma et \Gamma dans l’interpréteur, Jupyter et la plupart des environnements
match(γs_regex, "abγ")
Texte multiligne
multi_line = """
This is a
multi-line string.
"""
print(multi_line)
Les éléments d’un tableau de base peuvent avoir un type. Un type par défaut est attribué.
a = Int[1, 2, 3]
# Int[1, 2, 3.14] retourne une erreur
b = [1, "2", 3.14159] # type inféré : Any
c = [1, 2, 3] # type inféré : Int
Les fonctions dont le nom se termine par !
modifient leur premier argument (conventionnellement), en respectant les types.
push!(a, 4)
push!(a, ℯ) # \euler
push!(b, ℯ)
Il est également possible de créer des dictionnaires avec Dict{k, v}
d = Dict{Int, Float64}(1 => π, 2 => ℯ)
cf. https://github.com/datacraft-paris/2211-Cuvelier-Julia/blob/main/2-dataframes.ipynb
Charger les paquets pour travailler
using DataFrames
using RDatasets
using Statistics
using Chain
Charger le jeu de données Iris.
L’indexation sélectionne ici un groupe de ligne et avec :
toutes les colonnes.
iris = dataset("datasets", "iris")[[1, 51, 101], :]
On peut également faire l’inverse et renvoyer les données d’une colonne.
iris = dataset("datasets", "iris")[:, :SepalLength] # renvoie un vecteur
iris = dataset("datasets", "iris")[:, [:SepalLength]] # renvoie un dataframe
Avec la fonction select()
, il est également possible de sélectionner une colomne par son numéro, son symbole ou une chaîne de caractère
select(iris, :SepalLength, 2, "PetalLength")
Toutes les colomnes sauf...
select(iris, Not(2)) # Not() est une fonction de DataFrames pas de Julia
Utilisation d’un prédicat
select(iris, Cols(endswith("Length")))
# endswith("Length") renvoie une fonction
# endswith("Length")("sepalLength") == true
# x -> endswith(x, "Length")
On ne peut pas sélectionner la même colonne plusieurs fois dans les arguments.
Il est également possible de transformer les données pour obtenir des informations plus intéressantes. Par exemple, il est possible de calculer la moyenne de la longueur des pétals.
combine
fusionne toutes les ligens en une seule avec l’opération indiquée.
On peut ainsi appliquer une fonction mean
sur toute une colonne :
combine(iris, :PetalLength => mean) # renvoie un dataFrame
Pour récupérer la valeur, le DataFrame peut être appelé comme une matrice :
combine(iris, :PetalLength => mean)[1, 1] # rapporte la valeur scalaire
Avec select
, on effectue l’opération ligne par ligne.
Avec la syntaxe précédente, select()
permet de créer une nouvelle colonne avec un calcul simple donné par une fonction à appliquer sur toute la colonne.
Retourner la valeur moyenne des longueurs de pétales dans tout le DataFrame et recopier cette valeur dans chaque ligne :
select(iris, [:PetalLength, :PetalWidth] => mean)
Il est possible de transformer plusieurs colonnes à la fois et de générer une nouvelle colonne. Attention, la fonction utilisée doit avoir le bon nombre d’arguments :
select(iris, [:PetalLength, :PetalWidth] => +)
Pour des opérations plus compliquées, il faudra écrire une lambda qui devra gérer les vecteurs complets pour chacune des colonnes pour faire les opérations d’un coup.
On peut définir une fonction anonyme qui prendra en argument les colonnes complètes (.+
) pour effectuer l’opération +
ligne par ligne.
Pour faire la somme des deux valeurs divisées par leur différence. Ici le point devant l’opérateur est utilisé pour dire que l’on va appliquer l’opération élément par élément. Sinon Julia aurait additionné les vecteurs.
select(iris, [:PetalLength, :PetalWidth] => (pl, pw) -> (pl .+ pw) ./ (pl .- pw))
On peut encore utiliser la même notation pour faire plusieurs opérations sur les colonnes. Ce qui produit le résultat de chaque fonction pour chaque colonne (produit cartésien) avec .=>
au lieu de =>
La fonction pour la transformation doit prendre toute la colonne en entrée, pas seulement la valeur sur une ligne. Si l’on souhaite réaliser une opération pour une ligne, on peut utiliser ByRow
:
select(iris, :PetalLength => ByRow(sqrt))
Si l’on veut renvoyer plusieurs valeurs à partir d’une colonne, il est possible de renvoyer un tuple avec une transformation AsTable
pour dire que la colonne ne doit pas contenir le tuple.
select(iris, :PetalLength => ByRow((pl) -> (pl, pl.^2)) => AsTable)
Ce genre d’opération peut être utilsée pour ajouter de nouvelles colonnes.
transform(iris, :PetalLength => ByRow((pl) -> (pl, pl.^2)) => AsTable)
Il est encore possible d’utiliser la syntaxe des tuple nommés pour attribuer un nom aux colonnes.
transform(iris, :PetalLength => ByRow((pl) -> (pl=pl, pl_sq=pl.^2)) => AsTable)
groupby
permet de grouper des lignes selon un critère, on peut alors applique combine
pour produire une valeur par groupe, comme une moyenne.
combine(
groupby(iris, :Species),
Not(:Species) .=> mean
)
Afin de faciliter le travail avec les DataFrame, on utilise la macro @chain
qui permet une réécriture complète du code. Ici les opérations sont réalisées successivement sur le DataFrame qui est implicite
@chain iris begin
groupby(:Species),
combine(Not(:Species) .=> mean)
end
@todo
https://github.com/datacraft-paris/2211-Cuvelier-Julia/blob/main/3-graphes.ipynb
https://github.com/JuliaLang/IJulia.jl
IJulia is a Julia-language backend combined with the Jupyter interactive environment (also used by IPython). This combination allows you to interact with the Julia language using Jupyter/IPython's powerful graphical notebook, which combines code, formatted text, math, and multimedia in a single document. IJulia is a Jupyter language kernel and works with a variety of notebook user interfaces. In addition to the classic Jupyter Notebook, IJulia also works with JupyterLab, a Jupyter-based integrated development environment for notebooks and code. The nteract notebook desktop supports IJulia with detailed instructions for its installation with nteract.
add IJulia
using IJulia
notebook()
Si Jupyter n’est pas installé, l’installer.
Julia dispose d’un très gestionnaire de paquets efficace, Pkg. Dans Julia, on accède au gestionnaire de paquets en tappant ]
. Tapper backspace
ou Ctrl+C
pour revenir au REPL de Julia.
Ajouter un paquet (dans Pkg)
add nom_du_paquet
Supprimer un paquet et ses annotations
rm --manifest nom_du_paquet
La commande status
ou st
permet de voir les paquets installés.
Pkg est conçu autour de la notion d’environnement. Il s’agit d’ensemble de paquets qui peuvent être propres à un projet ou partagés et sélectionnés par leur nom. Ces environnements sont décrits dans un fichier de manifest qui peut être versionné pour facilité la reproductibilité des projets. Cette solution permet de travailler avec des versions différentes de paquets installés à des endroits distincts en fonction de vos besoins ; une fonctionnalité est comparable à virtualenv
en Python.
Pour activer un environnement (dans Pkg)
activate nom_du_projet
Lorsqu’on utilise un projet existant, la commande instantiate
télécharge les dépendances manquantes ou vérifie que l’environnement est prêt à être utilisé (dans Pkg)
instantiate
Au lieu d’utiliser la commande activate
depuis Julia, il est également possible de spécifier lors du démarage l’environnement à utiliser
julia --project=somepath myscript.jl
Les fichiers Project.toml
contiennent les informations générales sur le projet (nom, id, auteurs) et les dépendances directes, tandis que Manifest.toml
contient la date exacte des versions des dépendances directes et indirectes.
Pour générer les fichiers d’un paquet (dans Pkg)
generate my_project
Un sous répertoire src
est destiné à accueillir le code
Pour répliquer le comportement par défaut proposé dans Visual Studio code.
https://log-dh.vercel.app/notes/2024-02-16_julia-gestion-des-environnements/
- Documentation de PKG https://pkgdocs.julialang.org/v1/
- « Modern Julia Workflows ». 2023. Consulté le septembre 10. https://modernjuliaworkflows.github.io/.
- Lauwens, Ben, et Allen Downey. s. d. Think Julia. How to Think Like a Computer Scientist. Consulté le 14 décembre 2019. https://benlauwens.github.io/ThinkJulia.jl/latest/book.html.
Tutoriel de Cuvelier