Mojo: point de vue d'un chercheur et dévelopeur Python

J’ai pris un peu de temps pour découvrir le nouveau langage de programmation Mojo .

Qu’est-ce que Mojo ? Selon le site officiel :

“Mojo est un nouveau langage de programmation qui comble le fossé entre la recherche et la production en combinant le meilleur de la syntaxe Python avec la programmation de systèmes et la métaprogrammation. Avec Mojo, vous pouvez écrire du code portable plus rapide que le C et interopérer de manière transparente avec l’écosystème Python”.

La page Why Mojo explique que le langage a été créé pour l’intelligence artificielle (IA). Mojo est présenté comme un “langage de programmation doté d’une puissante métaprogrammation “compile-time”, de l’intégration de techniques de compilation adaptatives, de la mise en cache tout au long du flux de compilation et d’autres caractéristiques qui ne sont pas prises en charge par les langages existants”. Il s’agit également de “prendre en charge les architectures de puces modernes” grâce à “l’infrastructure de compilateur open-source MLIR”.

“En outre, nous avons décidé que l’objectif à long terme de Mojo était de fournir un surensemble de Python (c’est-à-dire de rendre Mojo compatible avec les programmes Python existants) et d’adopter l’implémentation CPython pour le soutien de l’écosystème à long terme. Si vous êtes un programmeur Python, nous espérons que Mojo vous sera immédiatement familier, tout en vous fournissant de nouveaux outils pour développer un code sûr et performant au niveau du système qui nécessiterait autrement C et C++ en dessous de Python”.

Impressionnant ! Mais qu’est-ce que cela signifie vraiment ? En tant que chercheur (travaillant dans le domaine de la mécanique des fluides) utilisant pour mon travail principalement Python, j’ai écrit cette longue note sur Mojo pour répondre à quelques questions :

  • Qu’est-ce que Mojo ? Et que va-t-il devenir ?

  • Mojo peut-il être utile pour le calcul scientifique généraliste (c’est-à-dire pas seulement pour l’IA) ?

  • Quand sera-t-il vraiment utilisable ? Que manque-t-il actuellement ?

Les créateurs de Mojo ont produit un grand nombre de documents présentant leur nouveau langage, notamment la documentation et une présentation technique donnée lors de la conférence LLVM . Malgré ces ressources précieuses, je n’ai pas trouvé sur le web de descriptions ou d’analyses approfondies ne provenant pas des créateurs de Mojo. C’est ce qui me motive à proposer ce point de vue indépendant sur Mojo.

Qui est l’auteur de cette note?

J’étudie la turbulence et les instabilités dans les fluides influencés par des différences de densité (comme les océans et l’atmosphère, mais aussi l’air dans les pièces chauffées). Dans notre groupe, nous utilisons principalement des expériences (dans la plate-forme Coriolis ) et des simulations numériques.

J’ai une certaine expérience de l’utilisation de Python et d’autres langages (C++, Fortran, un peu de Julia, …) pour le calcul scientifique.

Je suis le créateur d’un projet (appelé Fluiddyn) utilisant Python pour mon domaine d’étude. J’ai écrit et je maintiens quelques paquets Python utilisés pour mes recherches, comme Fluidlab, Fluidimage, Fluidfft, Fluidsim, Snek5000, Fluidsimfoam, Formattex, Formatbibtex, etc. J’ai également créé Transonic, un paquet pour faciliter l’utilisation de Pythran et d’autres compilateurs Python.

J’enseigne un peu, principalement sur la turbulence, les instabilités, les écoulements géophysiques, Python et l’informatique scientifique (mais seulement à un niveau pour lequel Python est suffisant).

Le développement de Mojo a commencé en septembre 2022. Une première version très préliminaire a été publiée en mai 2023 uniquement pour les essais en ligne. Mojo est devenu accessible pour une utilisation locale sur des ordinateurs individuels en septembre 2023. À ce jour, la dernière version est Mojo 0.6.0, qui a été publiée en décembre 2023. Alors qu’il en est encore à ses débuts, Mojo a été utilisé pour construire quelques programmes impressionnants (par exemple des multiplications de matrices très rapides et portables ou Llama.mojo plus rapide que llama.cpp ). De plus, le langage et son site web sont maintenant dans une phase qui permet de mieux comprendre ce que Mojo va devenir dans les mois et les années à venir. Notez que ce n’était pas le cas il y a encore quelques mois, et que saisir l’essence du projet derrière Mojo est clairement un défi.

Caractérisation des languages de programmation

Avant d’étudier ce qu’est et ce que sera Mojo, il est utile de revenir sur certaines catégories courantes de langages de programmation.

  • Interprété ou compilé : traditionnellement, un langage peut être décrit comme interprété (capables d’être exécuté directement avec un interpréteur, sans compilation) ou compilé (nécessitant que le code soit traité par un compilateur pour générer un programme exécutable, compilation dite “ahead-of-time” - ATO). Cependant, cette distinction porte plus sur le type d’utilisation plutôt que sur des caractéristiques inhérentes du langage. Par exemple, il existe des “compilateurs” Python et des outils comme Jupyter permettent d’interpréter le langage C++. En outre, de nombreux interprètes modernes intègrent un compilateur qui génère du code machine de manière dynamique par le biais d’une compilation “juste à temps” (JIT) pendant l’exécution. Notamment, CPython, l’implémentation de référence de Python, n’intégrait jusque là pas de compilation JIT, mais la prochaine version de CPython (3.13) devrait commencer à utiliser ce type de techniques. D’autres implémentations de Python, comme PyPy et GraalPy, utilisent déjà une compilation JIT.

  • Statique ou dynamique : les langages peuvent également être caractérisés par leur degré de dynamisme. Les caractéristiques dynamiques concernent la capacité à modifier certains aspects d’un programme au cours de son exécution. Le typage statique implique l’association d’une variable à un type spécifique au moment de la compilation, tandis que le typage dynamique signifie que les variables (ou les “noms”) peuvent pointer vers des objets de n’importe quels types. D’autres caractéristiques dynamiques peuvent inclure la capacité d’ajouter ou de modifier des méthodes pour les types/objets et même de modifier les fonctions “builtins”. Python est une language très dynamique, mais on peut aussi écrire en Python des codes très statiques, avec de la stabilité de type (les variables pointant vers des objets du même type), et même de l’annotation explicite de type. Julia et R sont des exemples de langages dynamiques, tandis que C, C++, Rust et Swift sont représentatifs des langages statiques.

Pour être complet, nous devons également mentionner la notion de système de type fort ou faible. Python est fortement typé de sorte que 1 + “2” provoque une erreur.

Nous verrons que Mojo ne peut pas être facilement classé dans ces catégories. Mojo peut être interprété (avec la compilation JIT) ou compilé en code machine. De plus, Mojo est d’abord un langage statique mais a aussi des capacités dynamiques.

Warning: closed-source et tout jeune

Avant d’aborder les discussions sur les langages de programmation et Mojo, deux aspects cruciaux méritent notre attention.

Tout d’abord, il est essentiel de noter que Mojo est actuellement un projet à code source fermé sous la direction de la société Modular . Cette société est spécialisée dans l’intelligence artificielle (IA) et prévoit d’améliorer significativement le cadre technologique dans ce domaine. Les éléments constitutifs de cette stratégie sont (i) un nouveau moteur d’IA nommé MAX (quelque chose comme TensorFlow et PyTorch, mais d’une certaine manière compatible avec ces solutions) et (ii) un nouveau langage de programmation - Mojo. Bien que Modular affirme que Mojo n’est pas destiné à être un “produit” commercial et qu’ils écrivent qu’ils “prévoient de rendre progressivement Mojo open-source” [ 1 ] , le projet reste fermé à l’heure actuelle. Nous reviendrons plus loin sur cet aspect critique.

Deuxièmement, il est important de reconnaître que Mojo en est encore à ses débuts et, à mon avis, qu’il n’est pas encore prêt à être utilisé en dehors de Modular. Les créateurs de Mojo se sont concentrés sur le cœur du langage et sur les performances.

Lancer un nouveau language de programmation

Alors que des vieux langages de programmation comme Python, C, C++, Java ou Javascript continuent de dominer le paysage [ 2 ] [ 3 ] , le domaine de la recherche appliquée aux langages de programmation a connu une activité remarquable ces dernières années. Une vague d’innovation a donné lieu à l’introduction de nouveaux langages de programmation (pour n’en citer que quelques-uns, Zig, Vlang, etc.). Outre ces vétérans et ces nouveaux venus, il existe également quelques langages d’âges intermédiaires (comme Go, Rust, Julia et Swift) qui ont rassemblé des communautés actives autour d’eux.

Dans cette note, nous nous concentrerons principalement sur Rust, Julia et Swift. Rust, soutenu par la Fondation Mozilla, a vu le jour en 2006 et a été présenté publiquement en 2010, apportant des innovations notables par rapport au C++, en particulier en matière de sécurité avec son vérificateur d’emprunts (“borrow checker”). Ce langage système connait un grand succès et est utilisé pour de nombreuses applications, comme par exemple dans le développement de Firefox. Malgré ses qualités, Rust est connu pour sa courbe d’apprentissage abrupte, ce qui le rend potentiellement moins adapté au prototypage rapide dans le domaine scientifique. Le développement de Julia a commencé en 2009, et le langage a été officiellement lancé en 2012 - il y a plus de dix ans. Au fil des ans, Julia a mûri et trouvé sa place pour des applications de calcul numérique intensif, où il excelle. Swift, le dernier langage d’Apple, a commencé à être développé en 2010, et la première version a été publiée en 2014. Swift est passé à un modèle open-source en 2015.

Lancer un nouveau langage de programmation en 2023 est une idée audacieuse. Dans un paysage dominé par des choix robustes et matures, la concurrence entre les langages de programmation est redoutable. Un nouveau langage doit non seulement présenter des caractéristiques et des avantages nouveaux par rapport à ses concurrents directs, mais il doit aussi être suffisamment bon en général. Les avantages liés à l’adoption d’un nouveau langage doivent l’emporter sur les coûts associés à l’utilisation d’un langage moins établi. En outre, dans ce domaine, l’aspect pratique l’emporte souvent sur la pureté. Le cas de Julia par rapport à Python est très intéressant. Julia présente des avantages évidents par rapport à Python dans le domaine du calcul scientifique, en particulier de meilleures performances et de meilleures fondations techniques, permettant d’éviter le “problème des deux langages”. Cependant, Python est suffisamment bon, présente d’autres avantages et évolue assez rapidement pour qu’il n’y ait pas de changement significatif en faveur de Julia [ 4 ] .

La création et l’établissement d’un nouveau langage de programmation est une tâche monumentale. Si des ressources considérables sont naturellement allouées à la conception du langage et au développement de son interprèteur/compilateur, il faut également prendre en compte d’autres aspects tels que la création d’une communauté, d’outils pour les développeurs et la distribution de codes, de bibliothèques, de documentation, etc.

Notons également que la création, la construction et l’évolution des languages sont des processus lents. Ces constructions humaines sont si complexes que l’échelle de temps typique est plutôt de plusieurs années que de plusieurs mois. Dans les cas de Rust, Julia ou Swift, plusieurs années se sont écoulées entre le lancement des projets et leurs premières versions publiques. L’équipe Mojo a, quant à elle, opté pour une stratégie de publication précoce, initiant rapidement la présence publique de son projet. Malgré cela, ils sont toujours dans un mode de développement rapide et fermé avec une petite équipe cohérente. Les progrès impressionnants réalisés en un peu plus d’un an suscitent la curiosité quant à la trajectoire future du projet.

Pourquoi Mojo a ses chances

Pourquoi devrions-nous nous intéresser à ce tout jeune langage de programmation ? A mon humble avis, des raisons convaincantes suggèrent que Mojo pourrait émerger comme l’un des langages les plus importants dans les années à venir :

  • Mojo est une réponse à un état réellement sous-optimal pour le calcul (scientifique et IA) dominé par des langages avec des faiblesses évidentes (bien sûr Python et C++, mais aussi Julia et Rust). Ce serait une nette amélioration d’avoir un nouveau langage avec un meilleur équilibre entre convivialité/performance/sécurité, avec des capacités de compilation JIT/OAT et une courbe d’apprentissage douce, en particulier pour les personnes familières avec Python.

  • Mojo est fondé sur une intuition très intéressante : un langage statique et sûr avec de fortes capacités dynamiques et une intégration transparente avec Python.

  • Mojo est porté par des personnes très intelligentes et renommées, en particulier Christ Lattner , le “cofondateur de LLVM, du compilateur Clang, de l’infrastructure du compilateur MLIR et du langage de programmation Swift” (extrait de sa page Wikipédia). Ses antécédents impressionnants dans des projets open-source importants et couronnés de succès ajoutent une couche substantielle de crédibilité. Bien que d’autres personnes accomplies aient contribué à la création de Mojo, le CV remarquable de Chris Lattner se distingue.

  • Mojo repose sur des bases techniques très solides, LLVM et MLIR, comme nous le verrons plus loin. “Mojo est le premier langage majeur conçu expressément pour MLIR” [ 5 ] .

  • Mojo semble avoir un financement solide et à long terme, soutenue par une entreprise sérieuse, avec des partenariats avec Amazon Web Services (AWS) et NVidia .

Les motivations qui ont conduit à la création de Mojo sont décrites en particulier dans la page Why Mojo . Il s’agit en grande partie de corriger la pile technologique utilisée dans l’IA, dominée en particulier par Python et C++. Cela positionne Mojo comme une nouvelle tentative de résoudre le “problème des deux langages”, ce qui a été une des motivations principales de Julia. Cependant, les approches adoptées par Julia et Mojo pour résoudre ce problème diffèrent de manière significative, à la fois en termes d’essence technologique (qui sera discutée plus tard) et dans leur relation avec Python. De manière quelque peu provocatrice, Julia et sa communauté sont perçues comme étant “contre Python”, tandis que Mojo vise à devenir un nouveau membre de la famille Python.

Lorsque Julia a été conçu en 2009, l’empreinte de Python dans le domaine du calcul scientifique était relativement modeste. Numpy n’a été introduit qu’en 2006 et Python 2.7 n’a été publié qu’en 2010. Pendant cette période, Matlab occupait une position plus dominante. Par conséquent, la conception de Julia s’est orientée vers Matlab plutôt que de s’aligner étroitement sur Python-Numpy. De plus, il n’est pas pratique d’utiliser Julia pour construire des librairies utilisables depuis Python (ou R d’ailleurs). L’approche “un langage pour tout” n’est pas très coopérative par nature. En revanche, la stratégie pragmatique consistant à utiliser des langages distincts pour des tâches différentes s’est avérée très efficace pour construire un écosystème scientifique Python robuste. Cet écosystème s’appuie sur des langages tels que C, Fortran, C++ et maintenant Rust [ 6 ] .

Bien que Julia soit formidable et dispose d’un excellent écosystème dans sa niche - le calcul numérique intensif - ce language a tendance à être quelque peu isolée. Pour la vaste communauté des utilisateurs de Python et des développeurs de librairies Python, il n’est pas très intéressant d’investir du temps dans Julia.

La stratégie alternative de Mojo concernant sa relation avec Python semble tout à fait raisonnable. L’introduction d’un langage compagnon et complémentaire pourrait trouver un accueil chaleureux au sein de la communauté Python. Compte tenu de la domination et de l’élan actuels de Python (voir la section suivante), ainsi que de l’inertie inhérente à la dynamique des langages, il s’agit d’un avantage très sérieux pour Mojo au cours de la prochaine décennie.

Projets améliorant Python

Pour mieux comprendre Mojo, il est utile de préciser ce qu’il n’est pas, en explorant quelques projets visant à améliorer Python et à remédier à ses faiblesses.

  • PyPy et GraalPy : Ces projets sont des implémentations alternatives de Python plus rapides que CPython. Cependant, leur adoption est limitée car ils ne peuvent pas accélérer les paquets qui reposent sur l’API C de CPython, tels que Numpy, Matplotlib, Pandas, scikit-learn, et d’autres.

  • Correction de l’API C de Python : des efforts sont en cours pour résoudre cette limitation. Il s’agit notamment de l’introduction de la nouvelle API C HPy (une API C pour Python indépendante de CPython) et d’une initiative à long terme plus ambitieuse visant à améliorer l’API C de CPython .

  • Développements de CPython : Les développeurs de CPython sont activement engagés dans des projets visant à améliorer les performances de CPython : no-gil, subinterpreters , et “Faster CPython”. CPython 3.13, qui sera utilisable à la fin de 2024, devrait avoir le GIL (Global Interpreter Lock) supprimée et un compilateur JIT basé sur l’approche Copy & Patch .

  • Compilateurs Python : divers compilateurs Python, tels que Pythran, Numba et Mypyc, permettent d’accélérer des sous-ensembles spécifiques (statiques) de Python, certains étendant la prise en charge à Numpy.

  • Calcul parallèle et avec GPU : Des projets innovants comme Codon et Taichi introduisent des “Langages Spécifiques à un Domaine” (DSL) en Python, adaptés au calcul parallèle et avec GPU.

Mojo se distingue de ces projets car il est un nouveau langage véritablement indépendant.

Mojo==Python++? En fait super different

Le site web de Mojo indique que l’un des objectifs à long terme de Mojo est de devenir un surensemble de Python, ce qui signifie que la plupart des programmes Python devraient fonctionner sans modifications. Je ne suis pas très optimiste à ce sujet, mais cela ne me semble pas si important. Pour exécuter du code Python, nous avons de toute façon d’autres bons interpréteurs Python. Je considère que certaines fonctionnalités de Python ne sont pas très utiles pour un projet comme Mojo, par exemple le support du module inspect (pour l’introspection dynamique), le “monkey patching” de n’importe quoi (même les builtins du langage) ou l’API C de CPython.

Quoi qu’il en soit, il est important de réaliser que Mojo est complètement différent de Python. Il y a quelques fonctions intégrées communes ( print , len , range , slice , …) et la syntaxe est largement compatible (imports, indexation, slicing, boucles, définitions de fonctions, contexts, try / except , async / await , même la définition de struct avec des “dunder” methods, …). Comme déjà écrit, Mojo peut également être “interprété” et dispose d’un REPL (qui peut être lancée avec la commande mojo). Mais certains types Python très importants sont encore absents (comme str , int , float , list , tuple et dict ) et presque aucune bibliothèque standard Python n’est disponible. De plus, plus profondément et de manière plus importante, la sémantique du langage est très différente. Par exemple, les variables Mojo ressemblent beaucoup plus à des variables C qu’à des variables Python : un nom dans Mojo est attaché à un objet en mémoire. Par exemple,

from memory.unsafe import Pointer

def print_pointer(ptr: Pointer):
    print(ptr.__as_index())

def main():
    a = 1
    p1 = Pointer.address_of(a)
    print_pointer(p1)

    a = 2
    p2 = Pointer.address_of(a)
    print_pointer(p2)

    # a = "mojo"  # error: cannot implicitly convert 'StringLiteral' value to 'Int' in assignment

affiche

140728977102328
140728977102328

ce qui signifie qu’il n’y a qu’une seule variable entière qui a été modifiée “inplace” par l’instruction a = 2 . En revanche, le code Python équivalent, quelque chose comme,

a = 1
print(id(a))
a = 2
print(id(a))
a = "python"  # valid

affiche

140163769254128
140163769254160

de telle sorte que id(2) - id(1) soit égal à 32. Le nom (la référence) a pointe d’abord vers l’objet entier 1 . L’instruction a = 2 modifie la référence (le nom a ) et non l’objet int .

En Python, les références sont partout (les noms sont des références, les éléments d’une liste/tuple sont des références, les arguments des fonctions sont passés en tant que références). Cela ne fonctionne pas comme ça avec Mojo, ce qui a des conséquences importantes. Par exemple, en Python a = "py" ; b = a crée deux références pointant vers un seul objet ( "py" ). En revanche, a = "mojo" ; b = a crée deux objets différents à deux endroits différents en mémoire, de sorte que

b = "mojo"
p = Pointer.address_of(b)
print_pointer(p)

c = b  # <- this really copies the String object "mojo"
p = Pointer.address_of(c)
print_pointer(p)

affiche quelque chose comme

140720822767840
140720822767856

Le contraste devient évident dans ces exemples, et on voit que Mojo offre aux développeurs un engagement plus direct avec les objets en mémoire. Contrairement à Python et Julia, Mojo n’utilise pas de comptage de références (“reference counting”) ni de ramasse-miettes (“garbage collector”). Au lieu de cela, la gestion de la mémoire, définissant la “durée de vie” (“livetime”) des objets, est automatiquement gérée par un vérificateur d’emprunts (“borrow checker”), comme en Rust. Néanmoins, Mojo offre également la flexibilité d’écrire du code rappelant le C, employant des pointeurs et une allocation et désallocation explicite de la mémoire. En conclusion, il devient évident que Mojo est avant tout un nouveau langage statique s’inspirant de C++, Rust et Swift. Ce langage est conçu pour la programmation système sûr.

Références et dynamisme dans Mojo?

Il est très surprenant de lire qu’un tel langage avec une sémantique de base si différente (par exemple la signification de l’opérateur égal) devrait devenir un surensemble de Python. Cependant, il est possible d’implémenter dans Mojo des objets agissant comme les références de Python. En fait, il existe déjà un type important dans Mojo, objet (qui est le type par défaut pour les arguments sans annotations de type), qui fonctionne de telle sorte que ce code agit comme en Python :

def modify(a):
    # argument given by copy in Mojo `def` but one can modify the referenced list
    a[0] = 1

def main():
    a = object([0])
    b = a  # <- the reference is copied but not the pointed list

    print(a)  # prints [0]
    modify(a)
    print(a)  # prints [1]
    print(b)  # prints [1]

Notez que Mojo n’utilise pas ici l’interpréteur Python. Il est également possible d’implémenter un objet référence (en utilisant le comptage de référence) dans Mojo. Je ne sais pas si un comptage de référence est utilisé en interne du type objet (Mojo est encore closed-source) ou si le “borrow checker” est suffisant pour imiter le comportement de Python sans comptage de référence et sans ramasse-miettes. Quoi qu’il en soit, puisqu’un nom est associé dans Mojo à un objet, l’imitation de Python nécessite des objets référence. Il est intéressant de voir que les deux comportements peuvent être obtenus dans le même langage.

Builtin références and références modifiables

Un document de travail de Mojo montre qu’on pourra implémenter des références d’objets simplement avec les mots clés ref et mutref .

+ de complexité pour + de contrôle et des codes statiques stricts

Comme mentionné précédemment, Mojo tire une forte influence des langages statiques C++, Rust et Swift. Cette influence introduit un niveau de complexité qui est totalement absent de Python.

Trois types différents de variables, immutabilité au niveau de la variable

En Python, l’immutabilité des objets est liée à leurs types (par exemple les int , str , tuple sont toujours immutables et les list et dict sont toujours mutables). En Mojo, comme dans d’autres langages statiques, les variables peuvent être explicitement marquées comme immuables ( let ) ou mutables ( var ). Par exemple, on peut déclarer une chaîne de caractères immuable dans Mojo, même si les chaînes de Mojo sont mutables, avec une syntaxe telle que let s = String("Mojo") .

En outre, Mojo introduit le mot-clé alias pour définir des variables “de compilation”. Notez que l’on peut exécuter n’importe quel code Mojo valide pendant la compilation, par exemple :

alias PI = 3.141592653589793
alias TAU = 2 * PI
alias my_alias = my_function_executed_at_compile_time()

Deux mots clés pour les définitions de fonctions ( def and fn )

Contrairement à Python, Mojo utilise deux mots-clés ( def et fn ) pour définir les fonctions. Il est essentiel de comprendre que ces deux mots-clés définissent les mêmes objets. Il ne s’agit pas du tout de “ def pour les fonctions de type Python et fn pour les fonctions statiques”. La différence entre les mots-clés concerne uniquement la mécanique de passage des arguments par défaut et la rigueur en termes de déclaration de variables. Par défaut, les arguments sont copiés pour def et empruntés (“borrowed”, référence immuable) pour fn . Avec fn , la spécification explicite des types des arguments, des paramètres et de la valeur de retour est obligatoire. Pour def , le type d’argument par défaut est object , représentant un type Mojo particulier conçu pour le code dynamique.

Meilleur contrôle du mécanisme de passage d’arguments

Outre la différence de mécanisme par défaut entre def et fn , il existe trois mots-clés permettant de modifier la manière dont les arguments sont transmis aux fonctions :

  • borrowed : référence immuable,

  • inout : référence mutable,

  • owned : objet donné à la fonction (utilisé avec un opérateur de déférence ^ à l’appel de la fonction).

Deux temps d’exécution et deux types d’arguments

Il est intéressant de noter que les alias ne sont pas comme les macros C (c’est-à-dire conduisant à de simples remplacements de code). Dans Mojo, il y a vraiment deux moments d’exécution : la compilation et l’exécution. Les fonctions et les structures peuvent être paramétrées, ce qui signifie qu’elles peuvent avoir à la fois des arguments d’exécution standard et des arguments de compilation (appelés “paramètres” dans Mojo). Par exemple, ici, a est un paramètre et b est un argument :

fn bar[a: Int](b: Int):
    print("bar[a: Int](b: Int)")

Ceci est assez similaire aux fonctionnalités de C++ ou de Rust, mais on peut exécuter n’importe quel code Mojo au moment de la compilation. Le decorateur parameter peut être utilisé pour définir des fonctions et des if à la compilation :

@parameter
if ...:
    ...

@parameter
fn my_closure[size: Int]():
    ...

Deux sortes de types: struct and class (pas encore dispo)

Alors que Python utilise le mot-clé class pour définir des types définis par l’utilisateur, cela n’est pas encore supporté dans Mojo. En Python, la variable de classe __slots__ peut être utilisée pour restreindre le dynamisme du type. Inversement, on peut en Mojo définir une struct , similaire à une struct C. Le comportement des types dans Mojo est contrôlé par des méthodes spéciales “dunder” (comme __init__ , __add__ , …), reflétant l’approche utilisée en Python. Sans support pour l’héritage, la composition est favorisée dans Mojo, s’alignant sur l’approche vue en Rust. De plus, Mojo 0.6.0 introduit un support de base pour les Traits (un ensemble d’exigences pour un type, très utilisé en Rust et Swift).

Définition de la durée de vie des struct

Dans Mojo, les développeurs ont un contrôle précis sur la durée de vie des types en choisissant d’implémenter ou d’omettre les méthodes “dunder”. L’initialisation est gérée par __init__ , la copie (“deep copy”) est contrôlée par __copyinit__ , et les opérations de “déplacement” (“shallow copy”) sont gérées par __movecopy__ .

Surcharge de functions

Python s’appuie sur le typage par canard (duck typing) et les vérifications de type à l’exécution (avec isinstance ) pour permettre aux fonctions de prendre en entrée des objets de types différents. En revanche, Mojo prend en charge la surcharge (comme C++ ou Julia), avec des paramètres et des arguments d’exécution.

Commentaires sur des caractéristiques actuels et futures de Mojo

Métaprogrammation de compilation

La métaprogrammation concerne du code qui traite de code. Un langage peut avoir des capacités de métaprogrammation qui aident un développeur à écrire du code pour comprendre/modifier d’autres morceaux de code. Une section de la documentation de Mojo compare et discute les différentes stratégies de métaprogrammation dans différents langages.

Par exemple, Python a des modules dans sa bibliothèque standard pour travailler avec le code Python, en particulier inspect et ast et une syntaxe pour remplacer des fonctions et des classes (les décorateurs). Dans Transonic , nous utilisons ces fonctionnalités pour isoler du code utile, produire de nouveaux fichiers et les compiler (au moment de la compilation), puis remplacer les fonctions par leurs équivalents compilés et optimisés au moment de l’exécution.

Julia dispose d’un autre puissant concept de métaprogrammation appelé macro . Les macros Julia fonctionnent de manière similaire aux fonctions mais sont exécutées lors de l’analyse du code (le parsing). Cela ressemble aux décorateurs de Python, mais avec la distinction que les macros de Julia peuvent prendre n’importe quelle expression en entrée, et pas seulement les définitions de fonctions ou de classes. En outre, elles agissent au moment de l’analyse sur le code plutôt que sur les objets.

Il est intéressant de noter que Mojo a (et aura encore plus) des capacités de métaprogrammation à la compilation . Dans la version actuelle de Mojo (0.6.0), il est beaucoup question (i) de fonctions et de structures paramétrées avec des arguments à la compilation (appelés “paramètres” dans Mojo), (ii) d’exécuter du code Mojo arbitraire (à la compilation) pour fixer les valeurs des paramètres et (iii) d’utiliser des conditions basées sur les valeurs des paramètres ( @parameter if ).

En outre, Mojo disposera de “décorateurs statiques [qui] sont des fonctions exécutées au moment de la compilation avec la capacité d’inspecter et de modifier l’IR” [ 7 ] . Bien qu’elles s’apparentent quelque peu aux macros Julia ou Rust, cette fonction opère à un niveau inférieur, manipulant la représentation intermédiaire MLIR au lieu du code source.

Un language haut-niveau pour le framework de compilation MLIR

Qu’est-ce que MLIR? From its Wikipedia page

MLIR ( Multi-Level Intermediate Representation ) est un cadre logiciel unificateur pour le développement de compilateurs. MLIR peut utiliser de manière optimale une variété de plates-formes informatiques.

Les représentations intermédiaires (IR) sont des langages situés entre les langages de programmation pour les humains et le code machine des unités de traitement. Les compilateurs effectuent généralement des optimisations au niveau des IR. D’après ce que j’ai compris, MLIR semble être un super cadre de IR avec différents “dialectes” et différentes cibles (les types d’unités de traitement, comme le CPU, le GPU, …). Au fur et à mesure que l’utilisation de divers accélérateurs et d’unités de traitement exotiques se répand, MLIR devrait gagner en importance. MLIR est très récente et il n’existe pas de langage de haut niveau conçu pour l’utiliser. L’un des objectifs des créateurs de Mojo est de créer un tel langage : “Mojo est le premier langage majeur conçu expressément pour la MLIR” [ 5 ] . Un tutoriel Mojo dédié explore cette fonctionnalité.

Intégration Python intégrée

Il est très facile d’exécuter du code Python à partir de Mojo en utilisant l’interpréteur Python en particulier pour utiliser n’importe quel paquet Python. Après avoir importé le paquet avec quelque chose comme,

from python import Python

np = Python.import_module("numpy")

(au lieu de import numpy as np ), on peut simplement écrire du Python dans Mojo ! Comme Mojo utilise CPython sous le capot, l’exécution de Python dans Mojo de cette manière n’est pas plus rapide qu’avec CPython !

Comme attendu au vu de l’état actuel de Mojo, il y a des choses qui ne fonctionnent pas, par exemple l’appel d’une fonction Python avec un argument mot-clé, ce qui est assez ennuyeux pour certaines librairies, par exemple Pandas ! De plus, il est encore assez difficile de convertir des objets Python en objets Mojo. Ces problèmes devraient rapidement être résolus.

C interopérabilité

Mojo devrait être capable d’utiliser les bibliothèques C de manière transparente en important simplement une fonction comme from "math.h" import cos . Pour le calcul scientifique, nous aurons besoin de librairies Mojo similaires à mpi4py ou h5py, pour utiliser respectivement MPI et HDF5.

Que manque-t-il à Mojo pour être réellement utilisable pour le calcul scientifique ?

Nature open-source

Au-delà des considérations techniques, la question de la nature open-source de Mojo mérite une attention particulière. Si Mojo reste fermé, la constitution d’une communauté solide en dehors de Modular représente un défi considérable. Pour favoriser une adoption plus large, il est crucial pour Modular de faire passer Mojo à un modèle open-source une fois qu’il aura atteint un stade de maturité plus avancé. L’ouverture de Mojo et la mise en place d’une structure de gouvernance transparente pour le projet sont des étapes cruciales qui, en dépit de ses avantages techniques significatifs, seront essentielles pour que Mojo réalise son plein potentiel et connaisse un succès généralisé.

Important

Après la première version de cette note, quelques questions sur le Discord de Mojo et une issue sur Github , la réponse sur la FAQ mentionnée dans le paragraphe suivant a été modifiée! A la question Est-ce que Mojo sera rendu open-source? , Modular répond maintenant clairement “Nous prévoyons de rendre progressivement Mojo open-source” .

Il convient de noter que la déclaration de la FAQ concernant l’open-source, “Au fil du temps, nous prévoyons d’ouvrir des parties essentielles de Mojo, telles que la bibliothèque standard”, est problématique. L’ambiguïté provient de l’expression “core parts”, qui ne permet pas de savoir si certains composants essentiels de Mojo resteront fermés. Une déclaration plus claire telle que “les parties essentielles de Mojo” aurait été plus rassurante. La perspective que certaines “extensions de Mojo” restent fermées est acceptable, mais si un composant essentiel reste propriétaire, cela introduit une dépendance et une incertitude pour les utilisateurs et la communauté. Je pense que la communauté Python hésiterait à adopter Mojo si un élément à code source fermé est nécessaire. Tout en reconnaissant la nécessité pour Mojo de mûrir en tant que projet à source fermée, il est également nécessaire de clarifier les plans de Modular. Je ne vois pas comment Mojo pourrait réellement rivaliser avec des langages open-source largement adoptés si le plan est tel que suggéré dans la FAQ (c’est-à-dire en gardant à long terme des parties essentielles de Mojo propriétaires).

Coeur du language statique

Je mentionne tout d’abord quelques éléments listés dans la feuille de route de Mojo comme étant prioritaires.

Ces améliorations planifiées devraient être mises en œuvre de manière substantielle dans les mois à venir, ce qui entraînera des changements significatifs dans le langage. Nous devrons donc suivre l’évolution de Mojo au printemps 2024 pour mieux évaluer son potentiel.

Un peu plus de Python

Je pense que les développeurs Python ont besoin d’au moins des types Mojo imitant les str , list et dict de Python . Notez que les types list et dict homogènes (et d’autres API de la librairie standard de Python) sont en cours d’implémentation dans un dépôt tiers .

Interopérabilité avec Python

  • Possibilité d’appeler des fonctions Python avec des arguments de type mot-clé (très courant pour les API des paquets Python, voir par exemple Pandas/Polar)

  • Plus de conversions Python vers Mojo (par exemple Numpy array vers Mojo Tensor), sans avoir besoin d’écrire du code MLIR.

Packaging

Mojo a besoin d’une bonne solution de packaging (peut-être par une compatibilité avec PiPY et/ou Anaconda.org ?), associée à un outil de qualité comme pdm , Pixi ou Cargo .

Production d’extensions Python à partir de code Mojo

Permettre la compilation d’extensions Python directement à partir du code Mojo, peut-être par le biais d’un décorateur @export et d’une commande comme mojo build-py-ext , semble être une perspective d’avenir réalisable. L’utilisation de la nouvelle API HPy serait un choix logique. Cette fonctionnalité potentielle est actuellement absente du site Web de Mojo! Il serait donc intéressant de connaître le point de vue de l’équipe Mojo sur cette fonctionnalité , car elle est cruciale pour améliorer l’adoption au sein de la communauté Python.

Conclusions

Comme nous l’avons exploré, l’un des aspects distinctifs de Mojo est qu’il permet(tra) aux développeurs d’écrire du code statique et sûr, à l’instar de Rust. Simultanément, Mojo supporte du code dynamique potentiellement sans annotations de typage, comme en Python. Bien que ces deux aspects soient encore en évolution et appelés à s’améliorer dans les mois/années à venir, il est crucial de reconnaître que même si Mojo vise à devenir un surensemble de Python, il ne doit pas être simplement considéré comme un “Python++”. Il est plus juste de considérer Mojo comme un nouveau langage statique moderne fortement influencé par C++, Rust et Swift, mettant l’accent sur la performance, la portabilité et la capacité d’écrire du code générique pour du matériel exotique. Ce n’est qu’ensuite qu’on peut apprécier le fait que Mojo peut aussi être interprété (avec compilation JIT) et permet d’écrire du code dynamiquement typé, ce qui lui donne le potentiel de devenir un quasi-superensemble de Python.

Nous observons que Mojo présente des similitudes et des différences significatives avec Cython, un langage qui agit comme un surensemble de Python avec des capacités C. Contrairement à Mojo, le code Cython nécessite une transpilation en C et ne peut fonctionner indépendamment d’un interpréteur Python. Cython est largement adopté dans de nombreux logiciels à succès de la pile numérique Python, tels que scipy et scikit-learn. Cependant, son utilisation reste confinée à cette application de niche. S’il devient possible de créer des extensions Python à partir du code Mojo, Mojo pourrait constituer une alternative convaincante à Cython.

La trajectoire actuelle de Mojo suggère qu’il est conçu pour un succès à long terme. Modular envisage de construire un langage de programmation puissant conçu pour prospérer au cours des prochaines décennies, en positionnant Mojo comme un concurrent face à des langages statiques établis, matures et entièrement libres. Cependant, les incertitudes entourant la future licence de l’interpréteur/compilateur Mojo, avec en particulier des composants de base restant potentiellement propriétaires, ne semblent pas compatibles avec les ambitions affichées par Modular pour Mojo.

Important

Comme déjà mentionné, après une première version de cette note, les dires des employés de Modular et la FAQ sont maintenant clairs et cohérents : ils déclarent que Mojo sera progressivement rendu open-source . Je n’ai pas changé les conclusions car la question de l’ouverture des sources de Mojo est toujours importante, mais je pense qu’il est raisonnable d’anticiper un Mojo entièrement open-source dans les prochaines années.

L’équipe Mojo est en passe de livrer un langage exceptionnel pour le calcul haute performance dans les prochains mois. Compte tenu des progrès remarquables observés jusqu’à présent, il est raisonnable de penser que d’ici la fin de l’année 2024, Mojo sera techniquement robuste, avec une gestion améliorée de la durée de vie, un Enum semblable à Rust, une compatibilité avec Python, et bien d’autres choses encore.

La décision cruciale de rendre le cœur de Mojo entièrement open-source va avoir des conséquences significatives. S’il est entièrement ouvert, Mojo pourrait avoir un impact transformateur, en particulier au sein de la communauté Python. Ce scénario nécessiterait un investissement de la part de l’équipe Mojo pour permettre la génération d’extensions Python à partir du code Mojo. L’élan open-source qui en résulterait pourrait rapidement renforcer l’écosystème Mojo, en favorisant le développement de nombreuses nouvelles bibliothèques. Au contraire, si le cœur de Mojo reste partiellement fermé, l’adoption risque d’être modérée, de nombreuses personnes optant pour d’autres solutions open-source. Il est intéressant de noter que lorsque j’ai présenté Mojo à des développeurs expérimentés et hautement qualifiés, leur première réaction a souvent tourné autour de son statut de logiciel fermé et des incertitudes concernant son futur statut de logiciel libre.

J’ai tendance à être optimiste quant à la future nature open-source de Mojo parce que c’est dans l’intérêt de Mojo, et donc de Modular. Mojo est encore un oisillon en pleine croissance dans son nid closed-source. L’espoir est que, dans les années à venir, Mojo déploiera ses ailes, s’élevant aux côtés de ses grands homologues open-source dans les courants ascendants du développement collaboratif.

En conclusion, j’aimerais partager mon impression très personnelle de Mojo. En tant que développeur Python ayant de l’expérience dans les langages statiques et le calcul haute performance, l’utilisation et l’exploration de Mojo ont été à la fois agréables et stimulantes sur le plan intellectuel. J’ai eu l’impression d’être à la fois chez moi, en vacances et doté de superpouvoirs. Par contre, comme il n’est pas facile d’apprendre à conduire avec une navette spatiale, on peut se demander s’il sera bon de mettre Mojo entre toutes les mains.