Julia, le langage qui les réunifiera tous ?

Trois langages pour les développeurs fonctionnels,
Sept pour les statisticiens dans leurs instances R,
Neuf pour les hommes mortels destinés au Scala,
Un pour le Seigneur de l’Algèbre sur son sombre banc des C,
Au pays du XOR où s’étend la Science des Données,
Un langage pour les gouverner tous,
Un langage pour les trouver,
Un langage pour les amener tous,
Et dans la lumière les lier
Au pays du XOR où s’étend la Science des Données.

(Originellement tiré de l’histoire de l’Anneau unique, issu du Seigneur des Anneaux)

Matlab, R, Python, Octave, ou encore Lisp… autant de languages (et d’environnements de développement) utilisés aujourd’hui par les scientifiques de la donnée (ou Data Scientists).

Ces dix dernières années, avec le retour en force de l’apprentissage machine et du traitement de gros volumes de données, une disparité est apparue au sein même de la communauté de la Science de la Donnée.
En effet, plusieurs bibliothèques, cadriciels, et environnements de développement, sont aujourd’hui utilisés pour différents langages et écosystèmes de programmation — au point même de réinventer la roue à chaque fois, et ces constructions peuvent donner un nouvel avantage à appréhender un langage ou une bibliothèque par rapport à un/une autre : les performances, le dynamisme, le confort de la programmation via la notation mathématique, l’interopérabilité, ou encore l’interactivité (comme ce qu’offre jupyter).

Ces divers langages de programmation offrent un confort d’écriture et de prototypage très nettement supérieurs aux langages de programmation bas-niveau, mais ne sont néanmoins pas fonctionnels lors d’une mise en production.
En effet, des problèmes peuvent survenir des données dites “du monde réel”, notamment à cause du design du langage de programmation, ou de la bibliothèque utilisée, comme le temps de calcul et le temps de réponse des solutions pour un gros afflux concernant les données à utiliser.

Ainsi, chercher à résoudre programmatiquement un problème lié à la Science de la Données aujourd’hui consiste plus à :

  1. choisir son langage, sa bibliothèque, son cadriciel et son environnement de programmation préféré, et/ou nécessaire à la bonne conduite du projet,
  2. prototyper la solution afin de répondre au problème le plus “rapidement” possible,
  3. (dans le cas où le prototype amène à des réponses satisfaisantes) implémenter une solution moins coûteuse en termes de temps d’exécution, consommation mémoire et CPU/GPU, via une implémentation bas-niveau du prototype.
  4. la diversification de la communauté dans la choix de la technologie lors de la première étape
  5. le fait qu’un scientifique de la donnée devrait maîtriser une compétence supplémentaire en programmation : l’utilisation d’au moins un langage de programmation bas-niveau, comme le C ou le C++.Si ce n’est pas le cas, il sera alors nécessaire de laisser ce travail à une autre équipe de développeurs, et le projet aura nécessiter d’engager deux équipes distinctes pour résoudre un seul et même problème.

Pourquoi Julia ?

Julia est un projet sous licence MIT, créé en 2009 sous l’impulsion de Jeff Bezanson, Stefan Karpinski, Viral Shah, et Alan Edelman.

L’environnement de développement — qui se compose du langage, des interfaces utilisateur et des bibliothèques — est sous licence GPL.

Julia a été pensé sur la base de cette fameuse réplique :

“Un pour tous, et tous pour un.”¹

, et les développeurs explicitent les intentions sur la raison même du projet :

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.¹

Traduit en Français :

Nous voulons un langage open source, pourvu d’une licence tolérante.
Nous voulons la rapidité du C, couplée au dynamisme du Ruby.
Nous voulons un langage homoiconique, doté de vraies macros comme Lisp, mais avec une notation mathématique évidente et familière, comme celle de Matlab.
Nous voulons quelque chose d’aussi facile à utiliser pour la programmation générale que Python, aussi facile pour les statistiques que R, aussi naturel pour le traitement des chaînes de caractères que Perl, aussi puissant pour l’algèbre linéaire que Matlab, aussi bon pour assembler des programmes que l’interpréteur de commandes. Quelque chose de très simple à apprendre, tout en gardant les bidouilleurs heureux.
Nous voulons que le langage soit interactif, et nous voulons qu’il soit compilé.

Aujourd’hui, Julia devient de plus en plus populaire dans le monde de l’open source, et de l’entreprenariat.

En effet, une entreprise s’est créée récemment afin de promouvoir la technologie pour de multiples solutions, et pouvoir exploiter totalement les capacités de ce langage (et son environnement de développement) dans un milieu lucratif et productif.

Vous pouvez télécharger le compilateur et l’écosystème de programmation Julia, en version 1.0.0 depuis Août 2018, sur le site officiel².


Les avantages de Julia

Les performances

La grande force de Julia est son compilateur à la volée, lui permettant d’approcher les performances d’un langage statiquement typé comme le langage C (Intel est d’ailleurs en train de travailler sur un transpileur Julia vers C).

Pour la compilation à la volée, la compilation est une première étape, et va réduire le code source à une représentation intermédiaire optimisable (du bytecode). Le bytecode est ensuite déployé sur le système cible, et va être traduit en code machine natif (pour Julia, la génération de code est du LLVM-MCJIT).

Il est à noter que le bytecode déployé est portable, à l’inverse du code machine pour une architecture donnée !

Ainsi, Julia génèrera du code machine natif au moment de l’exécution du programme, qui sera ensuite interprété, ou bien exécuté par une machine virtuelle.

De plus, Julia apporte la possibilité d’écrire du code parallélisable beaucoup plus facilement que pour Python, en guise d’exemple.

Si vous êtes intéressé par les micro-benchmarks, vous pouvez consulter ce répertoire Github. De même, si vous êtes intéressé par les macro-benchmarks, vous pouvez consulter cet article de blog associé aux travaux communs de la communauté scientifique de la NASA.

Enfin, il est à signaler que ces performances, dans de nombreux problèmes, ont permis d’intégrer le prototype au sein d’un environnement de production rapidement, sans devoir réimplémenter le tout avec un langage bas niveau.

Une syntaxe concise, et de nouvelles features

Afin de clarifier les aspects syntaxiques du langages, abordons le sujet par le biais de la résolution de problèmes simples.

Essayons tout d’abord de calculer l’aire d’un cercle, en ayant seulement pour information son rayon :

Comme vous pouvez le constater, Julia supporte l’introduction de caractères Unicode, et permet son utilisation (comme le caractère Pi), ce qui permet de faciliter le confort visuel, et la compréhension, lors de la lecture d’une fonction mathématique.

Aussi, même si Julia peut être typé dynamiquement, rien ne nous empêche de forcer le typage du/des paramètre(s) de la fonction, et de son retour.
Au contraire de Python et de son typage explicite à destination, principalement, du développeur, le but du typage explicite est de pouvoir soulager les passes de compilation, et donc d’accélérer de processus.

Imaginons un autre exemple, plus concret : la construction d’une bibliothèque d’apprentissage automatique en Julia, juste pour s’amuser.

Pour cela, je vais d’abord devoir construire une structure de données contenant mes données, ainsi que plusieurs informations concernant ces données :

Cependant, rien ne va comme prévu (ce qui arrive très souvent dans notre métier), et il est possible d’obtenir au final des jeux de données qui ne contiennent pas des entiers du type voulu…

Pour pallier à ce problème, nous pouvons écrire une fonction, qui va me prendre un jeu de données en entrée, ne contenant aucun entier de type Int64, et qui me retournera un autre jeu de données contenant, lui, des entiers de type Int64.

Pour cela, nous avons besoin de transformer la structure de données Dataset en une structure de données contenant un type “abstrait”, T.

Voilà qui est bien mieux !
Grâce à T , j’ai maintenant plus de contrôle sur les types de données pouvant être pris en compte dans ma structure de données.

Désormais, ma structure de données abstraite peut à la fois supporter des entiers, des flottants, mais aussi par exemple des chaînes de caractères (si l’on reste sur des types issus de la bibliothèque standard).

Maintenant, implémentons une fonction pour le traitement d’un type Dataset considéré comme invalide.

Cependant, dans notre schéma de pensées, il serait plus clair d’écrire une seule fonction qui prendra en compte à la fois un jeu de données bien formé (et qui le renverra) et invalide (et qui le traitera pour en renvoyer un valide cette fois).

Nous appellerons cette fonction treatment, et nous utiliserons ici un dispatch multiple.
NB : le dispatch multiple ne doit pas être confondu avec le pattern matching — pour faire très simple, le dispatch multiple va pouvoir spécialiser une fonction d’après le type de la donnée prise en paramètre d’une fonction, tandis que le pattern matching jouera à la fois sur le type et la valeur de la donnée prise en paramètre.

Pour information, Number est un type abstrait — union des types associés aux entiers ainsi qu’aux flottants.

Cependant, le problème ici est que x, pour la deuxième fonction, peut-être n’importe quoi…

Changeons pour cela le comportement de la fonction, en utilisant une condition sur le paramètre de la fonction :

Parfait !

Grâce aux fonctionnalités de Julia, rendant sa syntaxe bien plus permissive que Python par exemple, il est désormais assez facile d’écrire de manière précise un moteur de traitement pour un jeu de données abstrait.

Le parallélisme dans le cœur du projet

Quand l’on vient à parler parallélisme, beaucoup me parlent de leurs problèmes à paralléliser leur code Python d’une façon concise, en garantissant un minimum de sécurité dans son exécution.

Personnellement, j’ai beaucoup de mal à bien paralléliser du code Python, et surtout à le rendre le plus optimal possible.

Tout un tas de librairies Python existe aujourd’hui pour garantir la parallélisation, ou le multi-threading, sans y arriver réellement. Pour palier à cette problèmatique, Julia intègre le parallèlisme et le multi-threading nativement, avec l’aide d’une syntaxe (très) légère.

Par exemple, essayons d’initialiser un tableau avec des valeurs aléatoires, en utilisant le parallélisme :

L’utilisation de cette fonctionnalité se fait via une macro, @parallel , et permettra de faire remonter l’information jusqu’au compilateur Julia, qui se chargera de son écriture.

Ou alors, essayons d’initialiser un tableau de carrés entre 1 et 100, avec l’aide du multi-threading :

De même, l’usage du multi-threading se fait via l’utilisation de la macro @threads , issue de la bibliothèque Threads de Julia.

De plus, Julia fournit un environnement d’exécution basé sur le passage de messages, afin de garantir l’exécution de programmes sur différents processus, et dans des mémoires séparées (le rêve pour le calcul distribué).
Pour en savoir plus sur la parallélisation de votre code Julia, vous trouverez plus de ressources ici.

Écriture des librairies Julia… en Julia !

Si vous faites par exemple du Python, et que vous souhaitez écrire une bibliothèque en profitant des performances de vos système, votre réflexe sera probablement de :

  1. passer par un langage bas-niveau, et venir ensuite envelopper (wrapper) votre API Python,
  2. ré-écrire un nombre assez conséquent de fonctionnalités internes et propres à Python,
  3. (ré-)écrire vos bibliothéques en Cython (ce qui se fait de plus en plus),
  4. adopter PyPy (et tout son lot de bugs).

Un écosystème ouvert

Bien entendu, Julia possède une interface de fonctions étrangères (ou Foreign function interface — FFI), garantissant l’utilisation de code C/FORTRAN dans votre code Julia. Ainsi, pas de problème pour y importer vos bibliothèques favorites, écrites avec l’aide de ces langages.

Julia offre une feature native permettant d’appeler des fonctions du langage C directement, sans passer par une API ou une enveloppe logicielle.

Aussi, les bibliothèques RCall et PyCall vous aideront à importer et intégrer du code R et Python dans votre environnement de développement (si jamais, par exemple, vous vous sentez toujours plus productif avec l’utilisation de la bibliothèque R ggplot2).

Cet exemple montre que nous sommes en mesure d’intéragir avec la console R, mais aussi d’importer et d’utiliser des librairies existantes dans un langage différent de Julia, plutôt pratique dans l’utilisation de bibliothèques optimisées pour une tâche précise, sans devoir à chaque fois réinventer la roue !

L’interactivité au centre même de l’expérience développeur

Le prototypage et l’interactivité sont des plus-values dans le milieu de la Science de la Donnée.
En effet, l’interactivité permet d’expérimenter et d’explorer facilement le jeu de données dans le but de trouver des indices, et de développer une intuition concernant, par exemple, sa distribution ou sa validité.

RStudio, Jupyter, et bien d’autres notebooks sont aujourd’hui utilisés pour intéragir avec les données, des modèles, ou de simples expérimentations, afin de visualiser rapidement comment se comporte notre régression, et de modifier les hyper-paramètres du modèle sans devoir exécuter de nouveau les nombreuses lignes de code précédentes.

L’aspect pratique de ces notebooks est devenu aujourd’hui essentiel dans le travail continu d’un scientifique de la donnée, et Julia l’a bien compris en instanciant un noyau Jupyter pour le langage de programmation.

Pour en savoir plus, n’hésitez pas à vous référer à la page du projet.


Les désavantages du projet (actuellement)

Les désavantages actuels de Julia sont fortement liés à sa jeunesse et son manque de projets, et non pas à sa technologie directement.
Ainsi, ces désavantages doivent être perçus comme temporaires, et ne sont en rien des arguments nécessaires pour ne pas commencer à jouer avec Julia.

Cependant, nous pourrions pointer du doigt :

  • un léger manque de tooling (l’intégration d’un environnement virtuel de programmation ne serait vraiment pas de refus…),
  • une communauté encore peu nombreuse (mais grandissante chaque jour !),
  • l’absence de killer-libraries (en exemple, Scikit-Learn, Numpy et Pandas sont, pour ma part, les trois grandes killer-libraries qui me font utiliser le langage Python tous les jours).

Conclusion

Julia est un nouvel éco-système tentant de ralier plusieurs points essentiels pour la communauté de la Science des Données : les performances, le confort de la programmation, la facilité de prototypage, le regroupement et l’intégration de bibliothèques existantes développées à l’aide d’autres langages.

Ainsi, Julia est devenu un symbole fort dans l’alliance de la productivité et de la production.

Aujourd’hui, comme le dit Viral Shah, un des créateurs et développeur core de Julia, dans cet article de Forbes :

“Amazon, Apple, Disney, Facebook, Ford, Google, Grindr, IBM, Microsoft, NASA, Oracle et Uber sont des utilisateurs de Julia, mais sont aussi à la fois partenaires et organisations embauchant des programmeurs Julia”.

Alors, désormais, pourquoi pas vous ?

  1. https://julialang.org/blog/2012/02/why-we-created-julia
  2. https://julialang.org

Pour aller plus loin…

Antonin Carette

Lire d'autres articles par cet auteur.