La plupart des langages de programmation sont livrés avec un squelette minimal et vous disent d'installer des packages pour tout le reste. Besoin de formater une date ? npm install moment. Besoin de hacher un mot de passe ? pip install bcrypt. Besoin de valider un e-mail ? Cherchez "regex email validation" sur Google et priez.
FLIN adopte l'approche inverse. Chaque fonction nécessaire pour construire une véritable application web est intégrée dans le langage lui-même. Pas d'imports. Pas de gestionnaire de packages. Pas d'arbre de dépendances. Quatre cent neuf fonctions, disponibles dès la première ligne de chaque programme.
Voici l'histoire de la conception, de la catégorisation et de l'implémentation d'une bibliothèque standard suffisamment complète pour remplacer des dizaines de packages tiers -- et pourquoi cette décision définit tout ce que FLIN représente.
La philosophie : tout ce dont vous avez besoin, rien de superflu
Quand Thales et moi nous sommes assis pour définir la bibliothèque standard de FLIN, nous avons commencé par une question que la plupart des concepteurs de langages ne posent jamais : de quoi un développeur qui construit une vraie application web a-t-il réellement besoin ?
Pas un jouet. Pas un script qui affiche "Hello, World!" et se termine. Une vraie application avec authentification utilisateur, validation de formulaires, transformation de données, requêtes HTTP, formatage de dates et manipulation de chaînes. Le genre d'application qui, en JavaScript, nécessite 400 packages dans node_modules avant d'écrire votre première ligne de logique métier.
Nous avons catalogué chaque appel de fonction dans trois projets réels -- une plateforme e-commerce, un tableau de bord SaaS et un système de gestion de contenu. Puis nous avons demandé : lesquelles de ces fonctions devraient être intégrées ?
La réponse était presque toutes.
flin// En JavaScript, cela nécessite 3 packages :
// - moment (formatage de dates)
// - validator (vérification d'e-mail)
// - lodash (manipulation de chaînes)
// En FLIN, cela n'en nécessite aucun :
name = " juste gnimavo ".trim.title
email = "[email protected]"
valid = email.is_email
joined = now.format("MMMM D, YYYY")
print("{name} ({email}) - Valid: {valid} - Joined: {joined}")
// "Juste Gnimavo ([email protected]) - Valid: true - Joined: March 26, 2026"Quatre fonctions. Quatre catégories différentes (texte, validation, temps, sortie). Zéro import.
Les chiffres : 409 fonctions réparties en 11 catégories
La bibliothèque standard complète se décompose comme suit :
| Catégorie | Fonctions | Exemples |
|---|---|---|
| Texte | 31 | upper, trim, split, replace, pad_start |
| Nombre | 28 | abs, round, sqrt, random, clamp |
| Liste | 34 | map, where, reduce, sort, group_by |
| Map | 16 | keys, values, merge, filter, invert |
| Temps | 26 | now, format, is_before, start_of_month |
| Fichier | 14 | read_file, write_file, file_exists |
| JSON | 3 | parse_json, to_json |
| HTTP | 5 | http_get, http_post, http_put, http_delete |
| Sécurité | 18 | hash_password, verify_password, jwt_sign |
| Validation | 42 | is_email, is_url, is_numeric, sanitize_html |
| Utilitaire | 22 | type_of, clone, env, print, assert |
| Math (avancé) | 48 | sin, cos, mean, median, std_dev |
| Introspection | 15 | fields_of, type_name, has_field |
| HOF | 12 | map, filter, reduce, flat_map, zip_with |
| Erreur/Debug | 18 | log_info, log_error, timer_start, measure |
| Géométrie | 24 | distance, area_circle, rotate_point |
| Performance | 8 | track_error, measure_latency, memory_usage |
| Assainissement | 25 | sanitize_html, escape_sql, strip_tags |
| Total | 409 |
Chacune est disponible sans instruction d'import. Chacune gère les valeurs none gracieusement au lieu de planter. Chacune suit la même convention de nommage : minuscules, underscores pour les noms composés, appels de style méthode sur les valeurs.
La convention d'appel : méthodes et fonctions
L'une des premières décisions de conception a été de supporter deux styles d'appel pour chaque fonction. Vous pouvez appeler une fonction sur une valeur en utilisant la syntaxe méthode, ou l'appeler comme fonction autonome avec la valeur comme premier argument :
flin// Style méthode (préféré pour la lisibilité)
name = "hello world"
result = name.upper
length = name.len
words = name.split(" ")
// Style fonction (même résultat)
result = upper(name)
length = len(name)
words = split(name, " ")Les deux styles se compilent vers le même bytecode. Le style méthode existe parce qu'il se lit naturellement -- name.upper coule mieux que upper(name) -- et parce qu'il permet le chaînage :
flinresult = " Hello, World! "
.trim
.lower
.replace(" ", "_")
.slice(0, 20)
// "hello,_world!"Cette chaîne se compile en une séquence d'opcodes qui opèrent sur la pile sans aucune allocation intermédiaire pour le pipeline lui-même. La syntaxe méthode n'est pas du sucre syntaxique autour d'appels de fonctions -- c'est une fonctionnalité de premier plan de l'émetteur de bytecode.
Sécurité null : le fondement
Chaque fonction intégrée gère none gracieusement. Ce n'est pas optionnel. Ce n'est pas une convention. C'est imposé au niveau de la VM.
flinname: text? = none
result = name.upper // Retourne none (ne plante pas)
length = name.len // Retourne none
contains = name.contains("x") // Retourne none
// Chaînage sécurisé
formatted = user.name?.trim.title
// Si user.name est none, toute la chaîne retourne noneEn JavaScript, null.toUpperCase() lance une TypeError. En Python, None.upper() lance une AttributeError. En FLIN, appeler n'importe quelle méthode sur none retourne none. Point. Pas d'exceptions. Pas de gestion spéciale requise.
Cette décision seule élimine toute une classe d'erreurs d'exécution qui affligent chaque application web. Le fameux "Cannot read properties of null" est l'erreur JavaScript la plus courante en production. En FLIN, elle n'existe pas.
Plongée par catégorie : fonctions texte
La manipulation de texte est la catégorie la plus utilisée dans toute application web. FLIN embarque 31 méthodes de chaînes, couvrant tout, des opérations basiques à la validation en passant par l'encodage.
Les fonctions texte sont organisées en groupes logiques :
Opérations basiques -- les fonctions que vous appelez des dizaines de fois par fichier :
flintext.len // Longueur en caractères (pas en octets)
text.upper // "hello" -> "HELLO"
text.lower // "HELLO" -> "hello"
text.trim // " hello " -> "hello"
text.trim_start // " hello" -> "hello"
text.trim_end // "hello " -> "hello"Transformations de casse -- essentielles pour générer des slugs, des classes CSS et des noms de variables :
flintext.capitalize // "hello world" -> "Hello world"
text.title // "hello world" -> "Hello World"
text.snake_case // "helloWorld" -> "hello_world"
text.camel_case // "hello_world" -> "helloWorld"
text.pascal_case // "hello_world" -> "HelloWorld"
text.kebab_case // "hello_world" -> "hello-world"Validation -- parce que vérifier les entrées est quelque chose que chaque application fait :
flintext.is_empty // true si ""
text.is_blank // true si uniquement des espaces
text.is_numeric // true si tous des chiffres
text.is_alpha // true si toutes des lettres
text.is_email // Validation basique d'e-mail
text.is_url // Validation basique d'URLCes méthodes de validation ne sont pas des expressions régulières que les développeurs copient depuis Stack Overflow. Ce sont des fonctions Rust compilées qui s'exécutent à vitesse native. is_email ne vérifie pas simplement la présence d'un @ -- elle valide la structure selon la RFC 5322 (avec les simplifications pragmatiques que chaque validateur d'e-mail réel utilise).
Plongée par catégorie : fonctions liste
Les listes sont la deuxième structure de données la plus utilisée dans les applications web (après les chaînes). Les fonctions de liste de FLIN sont conçues pour le chaînage de méthodes, permettant un style de programmation fonctionnelle sans la cérémonie :
flin// Trouver les 5 meilleurs utilisateurs actifs par score
top_users = users
.where(u => u.is_active)
.sort_by(u => u.score)
.reverse
.take(5)
.map(u => u.name)Cela se lit comme de l'anglais. Filtrer les utilisateurs actifs. Trier par score. Inverser (les plus hauts d'abord). Prendre cinq. Extraire les noms. Chaque méthode retourne une nouvelle liste, donc la chaîne est immuable -- la liste users originale n'est jamais modifiée.
Les fonctions d'agrégation gèrent les opérations mathématiques courantes :
flinnumbers = [10, 20, 30, 40, 50]
numbers.sum // 150
numbers.average // 30.0
numbers.min // 10
numbers.max // 50
numbers.product // 12000000
// Comptage avec prédicat
active = users.count(u => u.is_active)Plongée par catégorie : fonctions temps
La gestion du temps est notoirement difficile. L'objet Date de JavaScript est largement considéré comme l'une des pires API du langage. Le module datetime de Python nécessite l'import de trois classes différentes. FLIN rend le temps simple.
flin// Heure actuelle
right_now = now
today_start = today
yesterday_start = yesterday
// Composantes temporelles
right_now.year // 2026
right_now.month // 3
right_now.day_of_week // 3 (mercredi)
right_now.is_weekend // false
// Arithmétique temporelle avec syntaxe naturelle
next_week = now + 7.days
two_hours_ago = now - 2.hours
deadline = today + 3.months
// Formatage
right_now.format("YYYY-MM-DD") // "2026-03-26"
right_now.format("MMMM D, YYYY") // "March 26, 2026"
right_now.from_now // "just now"Les constructeurs de durée -- 7.days, 2.hours, 3.months -- sont intégrés dans le type nombre. Ce n'est pas du chaînage de méthodes sur les nombres ; ce sont de véritables littéraux de durée que le système de types comprend. Vous ne pouvez pas ajouter 7.days à une chaîne. Vous ne pouvez pas soustraire 2.hours d'un booléen. Le compilateur attrape ces erreurs avant que le code ne s'exécute.
Plongée par catégorie : client HTTP
Construire des applications web signifie faire des requêtes HTTP. Dans la plupart des langages, cela nécessite une bibliothèque tierce. En JavaScript, l'API native fetch a mis des années à se standardiser et nécessite encore des polyfills dans certains environnements. En Python, vous avez besoin de requests ou httpx.
FLIN inclut un client HTTP en tant que fonction intégrée :
flin// Requête GET
response = http_get("https://api.example.com/users")
// POST avec corps et en-têtes
response = http_post("https://api.example.com/users", {
body: { name: "Juste", email: "[email protected]" },
headers: { "Authorization": "Bearer {token}" },
timeout: 30.seconds,
retry: 3
})
// Gestion de la réponse
{if response.ok}
users = response.json
print("Got {users.len} users")
{else}
print("Error: {response.status}")
{/if}Le client HTTP supporte GET, POST, PUT, PATCH et DELETE. Il gère la sérialisation JSON automatiquement (si le corps est un map ou une entité, il le sérialise en JSON et définit l'en-tête Content-Type). Il supporte les timeouts, les retries et les en-têtes personnalisés. Le tout intégré. Le tout sans import.
Pourquoi pas un gestionnaire de packages ?
La question évidente : pourquoi intégrer 409 fonctions dans le langage au lieu de fournir un gestionnaire de packages et laisser la communauté créer des bibliothèques ?
Trois raisons.
Premièrement, l'enfer des dépendances est réel. Le projet JavaScript moyen a 1 200 dépendances transitives. Le projet Python moyen en a 40. Chaque dépendance est une vulnérabilité de sécurité potentielle, un changement cassant potentiel et un problème de licence potentiel. Quand nous avons construit Déblo.ai, le requirements.txt du backend avait 89 entrées. Le node_modules du frontend contenait 1 847 packages. Pour une plateforme éducative. FLIN élimine tout cela.
Deuxièmement, la cohérence compte. Quand chaque développeur utilise la même fonction is_email, chaque application FLIN valide les e-mails de la même façon. Quand chaque développeur utilise la même fonction format pour les dates, chaque application formate les dates de manière cohérente. Il n'y a pas de débat sur quelle bibliothèque de validation utiliser. Il n'y a pas d'article de blog intitulé "Top 10 des bibliothèques de dates pour FLIN". Il y a une seule façon de le faire, et ça marche.
Troisièmement, FLIN cible les développeurs qui ne veulent pas gérer d'infrastructure. Le même développeur qui écrit <Button label="Save" /> sans instruction d'import ne veut pas gérer un package.json avec 47 dépendances. La promesse de FLIN est : écrivez votre logique applicative, et le langage gère tout le reste.
flin// Ceci est une application FLIN complète.
// Pas de package.json. Pas de requirements.txt. Pas de go.mod.
// Juste ce fichier.
entity User {
name: text
email: text where is_email
joined: time = now
}
users = User.all.sort_by(u => u.joined).reverse.take(10)
<div class="container">
<h1>Recent Users</h1>
{for user in users}
<Card>
<Avatar name={user.name} />
<Text>{user.name}</Text>
<Text size="sm" color="muted">{user.joined.from_now}</Text>
</Card>
{/for}
</div>Base de données, validation, formatage temporel, composants UI et rendu réactif. Un seul fichier. Zéro dépendance.
Implémentation : comment 409 fonctions tiennent dans un seul binaire
Intégrer 409 fonctions dans un runtime de langage semble coûteux. Ce ne l'est pas -- si vous le faites correctement.
Chaque fonction intégrée est implémentée comme une fonction Rust qui opère sur la pile de valeurs de FLIN. Le mécanisme de dispatch est une simple instruction match sur les octets d'opcode :
rustmatch opcode {
0x30 => self.exec_string_upper(),
0x31 => self.exec_string_lower(),
0x32 => self.exec_string_trim(),
0x34 => self.exec_string_index_of(),
// ... 405 entrées de plus
}Chaque implémentation fait typiquement 5-20 lignes de Rust. La bibliothèque standard complète ajoute environ 8 000 lignes à la VM -- moins de 10 % du code total. L'augmentation de la taille du binaire compilé est d'environ 200 Ko. C'est le coût de l'élimination de chaque dépendance tierce dont un développeur FLIN aurait jamais besoin.
Le vérificateur de types connaît la signature de chaque fonction intégrée. Quand vous écrivez name.upper, le vérificateur confirme que name est une valeur text, que upper est une méthode valide sur text, et que le résultat est aussi text. Si vous écrivez 42.upper, vous obtenez une erreur à la compilation, pas un crash à l'exécution.
Le flux d'appel de méthode
Comprendre comment un appel de méthode comme text.split(",") devient du bytecode exécutable révèle l'élégance du système :
Source: text.split(",")
|
Parser: Expr::Call { callee: Expr::Member { object, property }, args }
|
TypeChecker: check_member() retourne la signature du type Function
|
Emitter: try_emit_string_method() émet l'opcode StringSplit
|
VM: Exécute l'opcode, pousse la liste résultat sur la pileLe parseur voit text.split(",") et produit une expression d'accès membre enveloppée dans une expression d'appel. Le vérificateur de types recherche split sur le type text, confirme qu'il prend un argument text et retourne une liste [text], et valide l'argument. L'émetteur reconnaît ceci comme une méthode de chaîne intégrée et émet l'opcode correspondant directement -- pas de surcoût d'appel de fonction, pas d'allocation de closure, pas de dispatch vtable. La VM dépile la chaîne et le séparateur, effectue le split en utilisant str::split de Rust, alloue la liste résultat et la pousse sur la pile.
Surcoût total comparé à une opération de split Rust écrite à la main : un dispatch d'opcode. C'est le coût d'avoir un langage programmable au lieu de logique codée en dur.
Syntaxe lambda pour les fonctions d'ordre supérieur
De nombreuses fonctions intégrées acceptent des prédicats ou des fonctions de transformation. FLIN utilise une syntaxe lambda concise :
flin// Paramètre unique
list.map(x => x * 2)
list.where(x => x > 0)
// Paramètres multiples
list.reduce(0, (acc, x) => acc + x)
// Corps en bloc pour la logique complexe
list.map(x => {
temp = x * 2
temp + 1
})Le lambda x => x * 2 se compile en une closure qui ne capture aucune variable et prend un argument. La VM l'alloue sur le tas exactement une fois, et l'implémentation de map l'appelle pour chaque élément. Il n'y a pas de création de liste intermédiaire -- map construit la liste résultat au fur et à mesure de l'itération, élément par élément.
Ce que nous avons appris
Concevoir une bibliothèque standard pour un langage de programmation est un exercice de retenue. La tentation est de tout inclure. La discipline est de n'inclure que ce dont les développeurs ont réellement besoin.
Nous avons tracé la ligne à "fonctions nécessaires pour construire une application web de production sans aucune dépendance externe". Cette ligne nous a donné 409 fonctions. Pas 50 (trop peu -- les développeurs auraient besoin de packages pour les opérations basiques). Pas 2 000 (trop -- la surface d'API serait impossible à apprendre).
Quatre cent neuf. Assez pour tout construire. Assez peu pour tenir dans votre tête.
Ceci est la partie 71 de la série "How We Built FLIN", documentant comment un CEO à Abidjan et un CTO IA ont construit un langage de programmation qui embarque tout ce dont un développeur web a besoin.
Navigation de la série : - [70] Persistence in the Browser - [71] 409 Built-in Functions: The Complete Standard Library (vous êtes ici) - [72] 31 String Methods Built Into the Language - [73] Math, Statistics, and Geometry Functions