La liste des changements par rapport à la version 0.6 est impressionnante : elle représente quatre-vingt-cinq kilooctets de texte !
Syntaxe
Une bonne partie des changements de cette version vient de la syntaxe du langage. Ainsi, la syntaxe de définition des fonctions est plus flexible. Les arguments peuvent être déstructurés automatiquement par le langage : range((min, max)) = max - min définit automatiquement une fonction à un seul argument, range, cet argument étant un tuple. L’avantage est que, dans le code de la fonction, on peut utiliser chaque élément de ce tuple sans devoir le déstructurer soi-même : une fonction aussi simple peut être écrite en une ligne, en gagnant en lisibilité. Cette syntaxe se mélange sans aucun problème avec d’autres arguments :
Code : | Sélectionner tout |
foo((a, b), c) = (a + b) * c
L’objet missing sert désormais à représenter des valeurs manquantes (comme NULL en SQL ou NA en R, voire comme NaN pour le calcul en virgule flottante). Elle se propage naturellement dans les opérateurs et les fonctions mathématiques selon une logique à trois valeur (1 + missing == missing, par exemple). Cet objet a pour type Missing. Il peut simplifier très largement des parties de code qui utilisaient Nullable{T} comme type pour gérer des données qui pourraient manquer : le type de ces données devrait maintenant être Union{T, Missing}, ce qui est aussi clair ; dans le code, il n’est plus nécessaire d’utiliser des artefacts comme get(v) pour récupérer la valeur dans le cas où une valeur existe.
Aussi, la gestion des opérateurs a été dopée. Outre le nouvel opérateur de comparaison ⟂, quelques caractères de combinaison d’Unicode (primes, exposants, indices) peuvent se combiner aux opérateurs prédéfinis. Par exemple, on peut ainsi définir le nouvel opérateur de somme des carrés : +₂(a,b) = a^2 + b^2. Ces possibilités seront utiles à certains paquets effectuant des mathématiques de haut vol, en rapprochant la syntaxe de la notation mathématique usuelle. Fidèle à son habitude, Julia ne rate donc pas une occasion de gérer une plus grosse partie d’Unicode. Toujours pour les opérateurs, mais en mode mineur, l’opérateur paire => (utilisé pour définir des dictionnaires, par exemple) peut être diffusé sur des listes (.=>).
Pour l’appel des macros, il devient possible d’utiliser des crochets en plus des parenthèses : @macroname[args] correspond à @macroname([args]). Cette possibilité est prévue pour l’implémentation facile de nouveaux types de tableaux, par exemple des tableaux statiques (StaticArrays.jl) : il est plus naturel d’écrire @SArray[1 2; 3 4] que @SArray([1 2; 3 4]).
Les énumérations peuvent être définies dans un bloc begin-end, plutôt que de devoir être définies sur une seule ligne :
Code : | Sélectionner tout |
1 2 3 4 5 | @enum Fruit begin apple=1 orange=2 kiwi=4 end |
La macro @isdefined permettra de déterminer si une variable locale est définie. Les tuples nommés, comme t=(a=1, b2=), font leur apparition : leurs champs peuvent être accédés par un numéro (t[1]) ou par un nom (t.a). De même, dans les fonctions générées (elles prennent en argument les types des arguments et génèrent un code hautement spécialisé), il devient possible de définir une fonction qui est à la fois générée et non générée (pour une implémentation non spécialisée) en utilisant la macro @generated pour délimiter les parties générées des autres.
Certaines syntaxes sont désapprouvées, en vue de leur assigner une nouvelle signification plus tard. Par exemple, begin ne peut plus apparaître dans une indexation : dans le futur, a[begin] pourrait sélectionner le premier item d’un tableau (tout comme a[end] sélectionne le dernier élément). Il n’est plus possible d’effectuer d’assignation au niveau d’une indexation : x[i=1] sélectionnait l’élément 1 du tableau et définissait une variable i (certains utilisaient cette possibilité pour se rapprocher de leurs habitudes en C, comme a[i += 1] pour sélectionner l’élément courant et incrémenter i) ; bientôt, cette syntaxe pourrait être récupérée pour passer des arguments nommés lors de l’indexation, ce qui serait utile pour la lisibilité de certains types de tableaux (notamment bidimensionnels : on pourrait écrire a[x=1, y=2], comme xarray en Python).
Bibliothèque standard
Le gestionnaire de paquets précédent, Pkg2, était un reliquat de Julia 0.2. Il avait un certain nombre de limitations, notamment pour indiquer des dépendances conditionnelles entre modules (par exemple, pour des fonctions dont leur seul objectif est de faciliter l’utilisation conjointe de deux modules). La description des paquets se faisait par une myriade de petits fichiers (quelques octets), ce qui faisait rapidement une performance horrible selon le système de fichiers ; en particulier, une version est décrite par un identifiant de commit Git, ce qui impose de cloner le dépôt Git d’un paquet pour l’installer (y compris tout l’historique, même s’il est très gros), puis d’utiliser Git pour vérifier la version installée (ce qui est d’une lenteur abyssale). Ces caractéristiques étaient très intéressantes dans le cas d’un écosystème en plein boom et où la proportion d’utilisateurs qui ne contribuent pas à de nouveaux paquets est assez faible, mais plus à un langage qui se veut utilisable par le plus grand nombre. Pkg3 évite ces écueils en repartant de zéro pour la description et l’installation de paquets. Le dépôt de paquets actuel est bien sûr importé directement dans celui de Pkg3 (une liste modérée des “meilleurs” paquets sera aussi disponible). Pkg3 gère aussi la notion d’environnement, c’est-à-dire un ensemble de paquets et de versions que l’on peut échanger rapidement (comme les environnements virtuels de Python).
Le protocole d’itération a complètement changé avec cette version, notamment en exploitant le travail effectué pour intégrer missing dans le langage avec une bonne performance. Auparavant, il fallait implémenter trois fonctions différentes : start pour déterminer l’état initial de l’itérateur, next pour récupérer l’élément courant et le prochain état, done pour indiquer si l’état est final. Maintenant, il n’y a plus que la fonction iterate : avec un seul argument (l’itérateur), elle doit retourner l’état initial et le premier élément ; avec deux arguments (l’itérateur et l’état), elle doit retourner le prochain élément et le prochain état… ou nothing si l’itérateur n’a plus d’éléments dans la collection.
La bibliothèque mathématique de Julia est en cours de réécriture : elle était écrite entièrement en C (openlibm), en repartant du code de FreeBSD et d’OpenBSD, mais avait ses limitations. Notamment, si une implémentation d’une fonction spéciale (logarithme, exponentielle, sinus, etc.) est fournie, elle ne vaut que pour un seul type ; si elle était écrite en Julia, la même implémentation pourrait être utilisée avec d’autres types (nombre à virgule flottante sur trente-deux ou soixante-quatre bits, voire à précision infinie, par exemple). La charge de maintenance est identique (le projet Julia doit toujours maintenir sa propre bibliothèque mathématique, celles des compilateurs n’étant pas toujours au niveau), mais les avantages sont présents — notamment celui de diminuer la barrière d’entrée pour les nouveaux contributeurs. Ce travail est toujours en cours.
Et vous ?
Qu'en pensez-vous ?