URL: https://linuxfr.org/users/linkdd/journaux/letlang-encore-un-nouveau-langage-de-programmation Title: Letlang, encore un nouveau langage de programmation Authors: David Delassus Date: 2022年01月06日T01:29:20+01:00 License: CC By-SA Tags: letlang, langage, programmation, fonctionnelle et langage_de_programmation Score: 52 Bonjour Nal, Cela faisait longtemps que je ne t'avais pas écrit, mais je n'avais pas grand chose de pertinent à dire, tu me pardonneras j'espère. Aujourd'hui, je vais te parler d'un de mes "side-project", j'ai nommé **Letlang**. Si tu as la flemme de tout lire, tu peux aller mettre en favoris le site web https://letlang.dev pour une lecture du soir. **DISCLAIMER:** Très peu de code existe pour l'instant, le projet en est à ses balbutiements. Même le design de la syntaxe et fonctionnalités ne sont pas définitifs. # Un peu de contexte Au cours de ma carrière, j'ai pu toucher à de nombreux langages de programmation, et je me suis spécialisés dans certains. Je regarde aussi les petits nouveaux qui génèrent de l'engouement, ou les propositions d'améliorations de langage existant (coucou [TC39](https://tc39.es/), un jour je comprendrais ton nom, promis). Pour chacun de ces langages, j'ai une liste de choses qui me déplaisent et une liste de choses que j'adore. J'en suis venu à me poser la question suivante:> A quoi ressemblerait mon langage idéal ? Celui dont la liste de choses qui me déplaisent n'existerait pas. Un papier, un crayon plus tard, je commençait à décrire **Letlang**. # Quelques amuse-gueules Tout d'abord, il s'agit d'un langage de programmation fonctionnelle, paradigme dont je suis tombé éperdument amoureux. Le compilateur est implémenté en Rust. Il s'inspire de [Elixir](https://elixir-lang.org) (mon petit favori), [Python](https://python.org), [Rust](https://rust-lang.org), [Go](https://go.dev) (et pourtant je déteste ce langage), [TypeScript](https://typescriptlang.org) et des mathématiques. ## Système de type **Letlang** a ce que j'appelle un **typage strict** (j'aime pas la notion de fort/faible) :> On vérifie la cohérence des types le plus souvent possible, à la compilation comme à l'exécution Cependant, à la différence de la plupart des langages de programmation, une valeur n'a pas un unique type. Au contraire, je m'inspire ici des mathématiques : - `42` est un entier mais aussi un réel - `(1; 2)` est un vecteur 2D mais aussi une matrice 2x1 De la même manière, en **Letlang**, c'est la définition d'un type qui détermine si une valeur lui appartient. Ainsi, je peux écrire : ``` class int(n: number) check { frac(n) = 0 } ``` Ici je défini un type `int` qui est construit à partir d'un `number` nommé `n`. Je lui rajoute un prédicat que tout `n` lui appartenant doit valider, ici: la partie décimale du nombre doit être 0. Les prédicats sont une logique d'ordre supérieur ([Higher Order Logic](https://en.wikipedia.org/wiki/Higher-order_logic)), je peux ainsi définir le type suivant : ``` class even(n: int) check { thereis k: int, n = 2 * k } ``` Le prédicat dit qu'il existe un entier tel que `n = 2 * k`, qui est la définition mathématique d'un nombre pair. Ainsi `42` appartient aux types `number`, `int` et `even`, alors que `43` appartient uniquement aux types `number` et `int`. On peut également combiner plusieurs types en un seul avec les opérateurs `|`, `&` et `!` : ``` class odd(n: int & !even) ``` Par définition, un nombre impair est un entier qui n'est pas pair. Pour terminer sur le système de type, on a les types génériques : ``` class ok(val: (:ok, T)) class err(val: (:error, E)) class result(val: ok | err) ``` Les paramètres d'un type générique peuvent être contraint à un autre type : ``` class vector2(xy: (T, T)) ``` Dans l'exemple ci-dessus, `vector2` et `vector2` existent, mais `vector2` génèrera une erreur de compilation. ## Null et undefined En **Letlang**, une variable peut ne pas avoir de valeur définie, mais la valeur `null` ou `undefined` n'existe pas. C'est particulièrement utile pour travailler avec des équations : ``` let x: int # x n'a pas de valeur, mais cela sera un entier let x> 0 # toujours pas de valeur, mais elle est positive x - 3 = 0 # cette équation possède une solution, donc l'expression retournera true 1 / x = 0 # aucune solution n'existe pour cette équation, donc l'expression retournera false ```> **NB:** `=` est un opérateur de comparaison, `==` dans la plupart des langages. L'opérateur d'assignation est `:=`. De la même manière, on pourra définir la valeur de `x` grâce au mot clé `let` : ``` let x: int # x n'a pas de valeur let x - 3 = 0 # une seule solution existe, x = 3 ``` Attention avec cette notation cependant: ``` let x: number let 1 / x = 0 # produira une erreur, car aucune solution n'existe. ``` Pour résumer : - le mot clé `let` est une assertion de la part du développeur au compilateur/runtime - les variables sans valeur définie peuvent être utilisées dans des comparaisons - une variable est définie si on lui assigne une valeur avec `:=` ou si l'ensemble des définitions `let` produit une unique solution ## Coroutines et Streams En **Letlang**, comme en Go, les fonctions n'ont pas de [couleur](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/). N'importe quelle fonction peut être exécuté en parallèle : - le mot clé `coro` transforme un appel de fonction en coroutine - le mot clé `join` permet d'attendre la fin d'une coroutine et d'en récupérer le résultat Un exemple vaut 1000 mots : ``` import stdlib as std func main(args: list) -> :ok { c := coro std.strlen("foo") # do other stuff let 3 = join c :ok } ``` Les streams sont des objets qui permettent de communiquer avec des coroutines : - envoyer une valeur dans un stream est instantané - lire une valeur depuis un stream est bloquant tant que le stream est vide ``` let s: stream let v: int # écrit dans le stream s << 42 # lit depuis le stream et place le résultat dans la variable v s>> v ``` Ce qui peut donner par exemple : ``` func double(s: stream) -> :ok { let x: int s>> x match x { -1 => :ok, _ => { s << (x * 2) double(s) } } } func main(args: list) -> :ok { let s: stream c := coro double(s) s << 1 << 2 << 3 << 4 << -1 let :ok = join c let (a, b, c, d): (int, int, int, int) s>> a>> b>> c>> d let a = 2 let b = 4 let c = 6 let d = 8 :ok } ``` ## Effets de bord et exceptions Faire un langage fonctionnel pur c'est bien, mais les effets de bords c'est quand même bien pratique parfois. Une fonction pure c'est une fonction qui, comme en math, retourne toujours le même résultat étant donné les mêmes paramètres. Si `f(x) = x + 1`, alors `f(1)` donnera toujours `2`. Les fonctions du style `readline()` ou `get_time()` retourneront des résultats différents, ce sont donc des fonctions impures. En **Letlang**, ce n'est pas possible de créer de telles fonctions. A la place, on va utiliser les **effets**. Un effet se déclare comme une signature de fonction : ``` effect log(level: "debug" | "info", message: string) -> :ok ``` On peut ensuite appeler cet effet grâce au mot clé `perform` : ``` let :ok = perform log("info", "hello world") ``` Cela permet de déléguer la gestion de l'effet à celui qui appelle le code l'utilisant, cela avec la structure de contrôle `do {}` : ``` let :ok = do { perform log("info", "hello world") } effect log("debug", _message) { :ok } effect log("info", message) { std.print(message) :ok } ``` Si un effet n'est pas géré par une clause `effect`, alors il sera propagé jusqu'au runtime de **Letlang**. Si le runtime ne connait pas l'effet en question, le programme plantera. Ainsi, le runtime **Letlang** fournit un ensemble d'effets "builtin" sur lesquels le développeur pourra se reposer pour interagir avec le monde extérieur. Ensuite, les exceptions sont un cas particulier d'effet : **elles ne rendent pas le contrôle au code qui les appelle**. C'est un mécanisme de retour prématuré : ``` let :oops = do { throw :error :never_reached } catch :error { :oops } ``` ## Ensembles infinis Allez, un dernier pour la route. **Letlang** peut travailler avec des ensembles infinis, ils sont construit à l'aide d'une variable et d'un prédicat : ``` s := { x: int | x> 0 } 42 in s # true -1 in s # false ``` Ici, le type de `s` est donc `set`. C'est important à cause du paradoxe de Russel. Imaginons 2 secondes que le type `set` existe (ce qui n'est pas le cas car `any` n'existe pas). On pourrait écrire : ``` s := { x: set> | x not in x } ``` Soit : L'ensemble de tout les ensembles qui ne se contiennent pas eux même. Posons nous la question suivante : *est-ce que `s` appartient à `s` ?* - si oui, alors le prédicat `x not in x` est faux, donc non en fait - si non, alors le prédicat `x not in x` est vrai, donc oui en fait Le simple fait que `any` n'existe pas nous empêche de créer de tels ensembles. Ouf. # Bravo Pour ceux qui ont tout lu jusqu'ici, voici une petite récompense : ![raclette](https://www.mafermeenville.fr/wp-content/uploads/2017/11/kit-raclette-ma-ferme-en-ville.jpg)

AltStyle によって変換されたページ (->オリジナル) /