Sortie de Julia 0.7 Alpha, le langage de programmation scientifique dynamique
Prépare sa première version stable

Le , par dourouc05

42PARTAGES

8  0 
Le langage de programmation Julia est prévu pour apporter les bienfaits en termes de productivité des langages dynamiques au monde extrêmement conservateur du calcul de haute performance. La première préversion Alpha du langage en version 0.7 est maintenant disponible et elle sera assez proche de la version 1.0. Elle contient toutes les fonctionnalités désapprouvées du langage, avec des avertissements (ces constructions donneront directement des erreurs avec Julia 1.0, prévue pour début août) : cette version 0.7 n’a que pour objectif de faciliter la transition de l’écosystème vers la 1.0. Les développeurs de Julia se sont inspirés des problèmes que Python a eus en passant de la version 2 à 3 : il sera possible, pour une très grande majorité des paquets, d’avoir un même code fonctionnel sur la version 0.6 (l’actuelle), la 0.7 ou sur la 0.7 et la 1.0.

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
Toujours dans les définitions de fonction, le conteneur d’arguments nommés (généralement, kw) est maintenant implémenté comme un tuple nommé (plutôt qu’une liste de paires). Ce conteneur apparaît lorsqu’une fonction ne définit pas tous ses arguments nommés : f(; kw...), par exemple. Les fonctions habituelles des dictionnaires fonctionnent alors naturellement : au lieu de devoir itérer dans toutes les entrées manuellement, on peut simplement utiliser haskey(kw, "arg"). Ceci n’est rendu possible que par l’implémentation des tuples nommés avec une excellente performance (qui a empêché cette manière de procéder jusqu’à présent). Petit supplément : une fonction peut maintenant marquer un argument nommé comme obligatoire, simplement en ne définissant pas sa valeur par défaut (ce qui peut être très utile pour la lisibilité du code).

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
Auparavant, il fallait impérativement écrire @enum Fruit apple=1 orange=2 kiwi=4, peu importe le nombre d’items.

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 ?

Une erreur dans cette actualité ? Signalez-le nous !

Responsable bénévole de la rubrique HPC : Thibaut Cuvelier -

Partenaire : Hébergement Web