
il permettrait de générer des résultats sans erreur avec n'importe quelle syntaxe
Brian Chen, ingénieur logiciel en sécurité chez Zoom, a créé un nouveau langage de programmation appelé Noulith. Construit sur Rust, Chen s'est inspiré de l'ouvrage « Crafting Interpreters » de Robert Nystrom. Parmi ses collaborateurs clés, on trouve : Gustav Westling et Dmitry Cheryasov. Ce nouveau langage de programmation a fait l'objet d'une grande attention sur Internet en raison de sa caractéristique principale, qui serait de donner « des résultats corrects ». Quelle que soit votre syntaxe, Noulith prétend donner des résultats exacts.
Les structures de données immuables (mais pas les variables) signifient qu’il est possible d’écrire matrix = [[0] ** 10] ** 10 ; matrix[1][2] = 3 et ne pas s'en inquiéter, au lieu de [[0] * 10 for _ in range(10)] que le langage Python impose par exemple à ses utilisateurs. Il est également possible d’utiliser librement des éléments comme clés dans les dictionnaires.
Mais, grâce aux manœuvres de mutation ou de copie à l'écriture en interne (alimentées par les pointeurs de comptage de référence surpuissants de Rust), le programmeur n'a pas à sacrifier les performances qu’il obtiendra en mutant des listes. Tout est un opérateur infixe ; presque tout peut être appliqué partiellement. Si vous pensiez que Scala avait beaucoup de souplesse syntaxique, attendez de voir ce que nous avons.
Noulith > 1 à 10 filter even map (3*)
[6, 12, 18, 24, 30]
Vous avez déjà voulu écrire x max= y en cherchant une valeur maximale dans une boucle compliquée ? Vous pouvez le faire avec Noulith. Ceci est possible avec n'importe quelle fonction.
« Vous savez que Python a ce cas limite où vous pouvez écrire des choses comme {1} et {1, 2} pour obtenir des ensembles, mais {} est un dictionnaire parce que les dictionnaires sont arrivés en premier ? Nous n'avons pas ce problème car nous ne distinguons pas les ensembles et les dictionnaires. »
La précédence des opérateurs est personnalisable et résolue au moment de l'exécution.
noulith> f := \-> 2 + 5 * 3
noulith> f()
17
noulith> swap +, *
noulith> f() # (2 times 5) plus 3
13
noulith> swap +["precedence"], *["precedence"]
noulith> f() # 2 times (5 plus 3)
16
noulith> swap +, *
noulith> f() # (2 plus 5) times 3
21
Imaginez tout le code d'analyse des opérateurs que vous n'aurez pas à écrire. Lorsque vous avez besoin d'un nombre arbitraire de niveaux de précédence d'opérateurs, et que vous êtes heureux d'évaluer les entrées.
Exécution du code Noulith
C'est un projet Rust standard, donc, en bref :
Installer Rust et le configurer
Clonez ce dépôt,
cargo run --release --features cli,request, crypto
Ceci vous amènera dans un REPL, ou vous pouvez passer un nom de fichier pour l'exécuter. Si vous voulez juste construire un exécutable pour pouvoir rénommer ou l'ajouter à $PATH, exécutez simplement cargo build --release --features cli,request,crypto et regardez dans target/release.
Aucune des options de la ligne de commande pour cargo run ou cargo build n'est nécessaire ; elles donneraient juste de meilleures performances d'exécution et des fonctionnalités pour un temps de compilation plus lent et une taille binaire plus importante. (Sans --release, les frames de pile sont si grands qu'un des tests fait déborder la pile...)
Quelques Fonctionnalités
- Typé dynamiquement ;
- Les listes et les dictionnaires devraient semblent ressembler à Python. Les listes sont des parenthèses : [a, b, c]. Les dictionnaires sont des accolades : {a, b, c}. Noulith ne prend pas la peine de créer un type d'ensemble distinct, « les dictionnaires se comportant souvent comme leurs ensembles de clés » ;
- Au plus haut niveau, les instructions sont de type C/Java/ if (condition) body else body, for (thing) body (pas le moderne if cond { body })Le if ... else est l'expression ternaire ;
- Les boucles For utilisent les flèches de gauche : for (x <- xs) .... Utilisez une flèche à double tête pour les paires indice-valeur ou clé-valeur : for (i, x <<- xs) ;
- Les lambdas se présentent comme suit : x, y -> x + y
- Pas sensible à l'espacement ou à l'indentation. Selon chien, les symboles des opérateurs peuvent être enchaînés librement comme en Haskell ou Scala.
Rappelons que Scala est un langage de programmation polyvalent à typage statique qui prend en charge la programmation orientée objet et la programmation fonctionnelle. Conçu pour être concis, Scala avec ses nombreuses décisions de conception vise à répondre aux critiques de Java. La version 3 du langage est une refonte complète du langage Scala.
Exemple
Quelque peu impératif :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 | for (x <- 1 to 100) ( o := ''; for (f, s <- [[3, 'Fizz'], [5, 'Buzz']]) if (x % f == 0) o $= s; print(if (o == '') x else o) ) |
Quelque peu fonctionnel :
for (x <- 1 to 100) print([[3, 'Fizz'], [5, 'Buzz']] map (\(f, s) -> if (x % f == 0) s else "") join "" or x)
Les nombres, les opérateurs arithmétiques et les comparaisons
Les nombres, les opérateurs arithmétiques et les comparaisons fonctionnent pour la plupart comme vous vous y attendez, y compris les opérateurs bit à bit de style C, sauf que :
^ est une exponentiation. À la place, ~ en tant qu'opérateur binaire est xor (mais peut toujours être unaire en tant que complément binaire). Ou vous pouvez simplement utiliser xor.
/ fait une division rationnelle parfaite comme dans Common Lisp ou autre. % fait un modulo signé de style C. // fait la division entière en arrondissant à l'inférieur, et %% fait le modulo apparié (Deux élements E1 et E2 sont dit appariés lorsque chaque valeur x1,i de E1 est associée à une valeur x2,i de E2).
La précédence
La précédence est quelque chose d'un peu raisonnable et plus simple, inspiré par la précédence de Go.
Go est un langage de programmation open source, développé par Google, qui facilite le développement de logiciels simples, fiables et efficaces. En raison de sa simplicité, il est utilisé aussi bien pour développer des applications, écrire des scripts pour de grands systèmes. Cette simplicité est nécessaire aussi pour assurer la maintenance et l'évolution des programmes sur plusieurs générations de développeurs.
S'il vise aussi la rapidité d'exécution, indispensable à la programmation système, il considère le multithreading comme le moyen le plus robuste d'assurer sur les processeurs actuels, cette rapidité tout en rendant la maintenance facile par séparation de tâches simples exécutées indépendamment. Cette conception permet également le fonctionnement sans réécriture sur des architectures multicœurs en exploitant immédiatement l'augmentation de puissance correspondante.
Plutôt que de suivre l'héritage du C, Noulith suit l’exemple du langage Go :
Code : | Sélectionner tout |
1 2 3 4 5 | Tighter ^ << >> * / % & + - ~ | Looser == != < > <= >= |
Comme en Python et en mathématiques, les opérateurs de comparaison peuvent être enchaînés comme 1< 2 < 3. Noulith possède également min, max, et l'opérateur de comparaison à trois valeurs <=> et son inverse >=<.
Les chaînes de caractères : comme en Python, Noulith n'a pas de type de caractère séparé ; l'itération sur une chaîne de caractères ne donne que des chaînes à un seul caractère.
Types de données :
- Null ;
- Nombres : grands nombres entiers, rationnels, flottants et complexes, qui coagissent de gauche à droite dans une liste selon les besoins. Notons qu'il n'y a pas de booléens, à la place, 0 et 1 sont utilisés ;
- Listes (hétérogènes) : [a, b]. Indexation et découpage à la fois dans la syntaxe et la sémantique des entiers négatifs ;
- Dictionnaires (hétérogènes) : {a : b, c : d}. Les valeurs peuvent être omises, dans ce cas, elles sont juste nulles, et sont utilisées comme des ensembles. Index my_dict[key], test key in my_dict. Si vous ajoutez un {:a}, c'est la valeur par défaut ;
- Chaînes de caractères : juste ce que Rust a, toujours des séquences d'octets UTF-8 valides ;
- Octets ;
- Vecteurs : listes de nombres, remarquables par le fait que la plupart des opérations sur ceux-ci se diffusent automatiquement, par exemple V(2, 3) + V(4, 5) == V(6, 8) ; V(2, 3) + 4 == V(6, 7) ;
- Streams : listes non dynamiques, générées uniquement dans quelques situations spécifiques pour le moment.
Les expressions
Tout est une fonction globale et peut être utilisé comme un opérateur ! Par exemple, a + b est simplement +(a, b) ; a max b est max(a, b). Dans un cas particulier, a b (lorsqu'il est entouré d'une autre syntaxe qui empêche de traiter l'un ou l'autre comme un opérateur binaire) est a(b) (ceci est principalement pour permettre le moins unaire), mais quatre identificateurs ou plus de taille égale et des élements similaires dans une rangée comme (a b c d) est illégal. (Aussi, faites attention au fait que a[b] est analysé comme indexant b dans a, et non pas a([b]).
De même : de nombreuses fonctions/opérateurs qui acceptent normalement deux arguments n'en acceptent qu'un seul et l'appliquent partiellement comme leur second, par exemple +(3) (qui, comme ci-dessus, peut être écrit +3 dans le bon contexte) est une fonction qui ajoute 3. (Ce n'est pas une syntaxe spéciale, juste une option de nombreuses fonctions ; + est défini pour prendre un ou deux arguments et s'il en prend un, il s'applique partiellement). Puisque - et ~ ont des surcharges unaires, Noulith fournit des alternatives comme subtract et xor qui s'appliquent partiellement lorsqu'elles sont appelées avec un argument, tout comme en Haskell.
Haskell est un langage de programmation fonctionnel fondé sur le lambda-calcul et la logique combinatoire. Son nom vient du mathématicien et logicien Haskell Curry. Il a été créé en 1990 par un comité de chercheurs en théorie des langages intéressés par les langages fonctionnels et l'évaluation paresseuse. Si vous appelez a(b) où a n'est pas une fonction mais b l'est, b applique partiellement a comme premier argument. C'est exactement comme les sections en Haskell.
Les identificateurs peuvent être constitués d'une lettre ou d'un _ suivi de n'importe quel nombre de caractères alphanumériques, de ', ou de ? ; ou de n'importe quel nombre consécutif de symboles valides pour être utilisés dans des opérateurs, y compris ? (Ainsi, par exemple, a*-1 ne fonctionnera pas parce que *- sera interprété comme un seul jeton. a* -1 ne fonctionnera pas non plus, mais pour une raison différente - il est interprété comme s'il commençait par appeler * avec a et - comme arguments.
a*(-1) ou a* -(1) fonctionneraient). Par rapport à des langages similaires, notons que : n'est pas un caractère légal à utiliser dans les opérateurs, alors que $ l'est. De plus, un certain nombre de mots-clés sont interdits, ainsi que toutes les lettres majuscules et tous les éléments commençant par des lettres majuscules suivies d'un guillemet simple (bien...
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.