Leçon 5/10 10 min

Des fonctions bien typées

Paramètres, retours, optionnels, callbacks : le contrat complet d'une fonction. Fini les appels avec un argument oublié.

Le grand classique JS : l'argument oublié

Tu connais ce scénario. Une fonction envoyerEmail(destinataire, sujet, corps), appelée quelque part dans la codebase avec deux arguments seulement. Le corps est undefined. L'email part. Vide. Personne ne s'en rend compte avant le client, qui reçoit un message avec l'objet « Votre commande » et… rien.

function envoyerEmail(destinataire, sujet, corps) {
  // corps peut être undefined : JavaScript s'en fiche
  expedier({ to: destinataire, subject: sujet, body: corps });
}

// Trois semaines plus tard, dans un autre fichier :
envoyerEmail('client@example.com', 'Votre commande');
//                                                ↑ oublié, pas une erreur en JS

JavaScript accepte n'importe quel nombre d'arguments, dans n'importe quel ordre de types. L'argument en trop ? Ignoré. Celui qui manque ? undefined. Personne ne vérifie à l'écriture du code : l'erreur ne surgit qu'à l'exécution, sur le chemin précis que personne n'a testé, chez un utilisateur, jamais en dev.

TypeScript, lui, vérifie le contrat avant d'exécuter quoi que ce soit. Une signature typée, c'est un accord formel : le compilateur compte les arguments, contrôle leurs types, et refuse tout appel qui trahit le contrat.

Prédis avant de lire

La signature est saluer(nom: string, poli?: boolean). Lesquels de ces appels passent : saluer(), saluer('Sam'), saluer('Sam', true), saluer(true, 'Sam') ? Écris ta prédiction, puis vérifie.

Voir la réponse

Seuls saluer('Sam') et saluer('Sam', true) passent. saluer() est refusé : nom est requis (pas de ?), il manque un argument. saluer(true, 'Sam') est refusé : l'ordre est inversé, boolean n'est pas un string, les types sont incompatibles. JavaScript, lui, aurait accepté les quatre sans un mot.

Le contrat complet d'une fonction

Une signature TypeScript fixe quatre choses à la fois :

// 1. Types des paramètres  2. Paramètre optionnel (?)  3. Valeur par défaut  4. Type de retour
function saluer(nom: string, poli: boolean = false): string {
  if (poli) {
    return 'Bonjour ' + nom + ', enchanté.';
  }
  return 'Salut ' + nom + ' !';
}

// 5. Callback typé : la forme exacte de la fonction qu'on attend
function filtrer(notes: number[], garder: (n: number) => boolean): number[] {
  return notes.filter(garder);
}

filtrer([8, 12, 18], (n) => n >= 10);   // ✓

Paramètre optionnel ? vs valeur par défaut. poli?: boolean signifie que l'argument peut être absent ; à l'intérieur de la fonction, le type devient boolean | undefined. poli = false signifie que si l'argument est absent, il vaut false ; TypeScript infère boolean, pas d'undefined à gérer. Dans les deux cas, l'appel sans ce paramètre est autorisé. Les paramètres optionnels ou avec valeur par défaut se placent toujours en dernier : sinon, le compilateur ne saurait pas quel argument correspond à quoi.

Typer un callback. La notation (n: number) => boolean décrit une fonction qui prend un number et renvoie un boolean. C'est un type comme les autres : tu peux le mettre dans un paramètre, une interface, un alias. Dès que le code passe le mauvais callback, le compilateur le détecte avant l'exécution.

Quand une fonction ne renvoie rien d'utile (elle agit par effet de bord : afficher, enregistrer, envoyer), son type de retour est void. C'est différent de undefined : void dit « n'utilise pas ce que je renvoie ».

Faut-il toujours écrire le type de retour ? TypeScript l'infère souvent. Mais sur les fonctions publiques (celles qu'appellent d'autres modules, d'autres développeurs, l'IA dans ton contexte), l'écrire documente le contrat et le verrouille. C'est exactement le sujet de la leçon 1 : la fonction qui oublie un return sur un chemin. Avec un type de retour explicite, l'erreur apparaît dans la fonction elle-même, pas dans les 30 appelants.

À toi : fais taire le compilateur

Voici un vrai compilateur TypeScript, chargé dans ton navigateur. Clique sur Vérifier les types : il va trouver l'erreur, sans exécuter une seule ligne. Lis le message d'erreur, corrige l'appel, et revérifie jusqu'au ✓ 0 erreur. Tu peux aussi cliquer sur Exécuter pour voir ce que le code produit une fois corrigé.

🧐 Labo TypeScript · le compilateur juge ton code (mode strict)
Bloqué sur la correction ? Voir le fix

Il faut au moins le nom : saluer('Sam'). Le paramètre poli est optionnel (le ?), mais nom est requis : sans lui, le compilateur affiche Expected 1-2 arguments, but got 0. Avec saluer('Sam'), ✓ 0 erreur et l'exécution affiche « Salut Sam ! ». Tu peux aussi essayer saluer('Sam', true) pour la version polie.

Ce que ça change avec l'IA

Une signature typée, c'est un prompt permanent. Quand l'IA voit filtrer(notes: number[], garder: (n: number) => boolean): number[] dans le contexte, elle sait exactement ce qu'elle doit générer comme callback : pas besoin de l'expliquer en langage naturel. Et si elle génère un callback qui renvoie un string au lieu d'un boolean, le compilateur le souligne en rouge avant même que tu exécutes quoi que ce soit.

L'autre bénéfice : le compilateur compte les arguments à ta place. Si l'IA génère un appel avec un argument oublié (comme notre envoyerEmail du début), l'erreur apparaît immédiatement dans l'éditeur. Fini l'email vide qui part chez le client.

Type tes fonctions publiques en premier, type de retour compris. C'est le meilleur ratio contrat/effort du langage : quelques caractères de plus, et chaque appel dans tout le projet est vérifié automatiquement. L'IA aussi.

The classic JS trap: the forgotten argument

You know this scenario. A function sendEmail(recipient, subject, body), called somewhere in the codebase with only two arguments. The body is undefined. The email goes out. Empty. Nobody notices until the client receives a message with the subject "Your order" and… nothing.

function sendEmail(recipient, subject, body) {
  // body can be undefined — JavaScript doesn't care
  dispatch({ to: recipient, subject: subject, body: body });
}

// Three weeks later, in another file:
sendEmail('client@example.com', 'Your order');
//                                           ↑ forgotten — not an error in JS

JavaScript accepts any number of arguments, in any order of types. An extra argument? Ignored. A missing one? undefined. Nobody checks at write time — the error only surfaces at runtime, on the exact path nobody tested, on a user's machine, never in dev.

TypeScript, on the other hand, checks the contract before anything runs. A typed signature is a formal agreement: the compiler counts the arguments, checks their types, and rejects any call that betrays the contract.

Predict before reading on

The signature is greet(name: string, polite?: boolean). Which of these calls compile: greet(), greet('Sam'), greet('Sam', true), greet(true, 'Sam')? Write your prediction, then check.

Show the answer

Only greet('Sam') and greet('Sam', true) compile. greet() is rejected: name is required (no ?), one argument is missing. greet(true, 'Sam') is rejected: the order is reversed, boolean is not a string — incompatible types. JavaScript would have accepted all four without a word.

A function's full contract

A TypeScript signature fixes four things at once:

// 1. Parameter types  2. Optional parameter (?)  3. Default value  4. Return type
function greet(name: string, polite: boolean = false): string {
  if (polite) {
    return 'Good day, ' + name + '.';
  }
  return 'Hi ' + name + '!';
}

// 5. Typed callback: the exact shape of the function we expect
function filter(scores: number[], keep: (n: number) => boolean): number[] {
  return scores.filter(keep);
}

filter([8, 12, 18], (n) => n >= 10);   // ✓

Optional ? vs default value. polite?: boolean means the argument may be absent — inside the function, the type becomes boolean | undefined. polite = false means if the argument is absent it's false — TypeScript infers boolean, no undefined to handle. In both cases, calling without that parameter is allowed. Optional or defaulted parameters always go last: otherwise the compiler can't tell which argument maps to which.

Typing a callback. The notation (n: number) => boolean describes a function that takes a number and returns a boolean. It's a type like any other — you can put it in a parameter, an interface, an alias. The moment code passes the wrong callback, the compiler catches it before execution.

When a function returns nothing useful (it acts by side effect — displaying, saving, sending), its return type is void. It's different from undefined: void says "don't use what I return".

Should you always write the return type? TypeScript infers it often. But for public functions — those called by other modules, other developers, the AI in your context — writing it documents the contract and locks it down. That's exactly the lesson 1 scenario: the function that forgets a return on one path. With an explicit return type, the error appears inside the function itself, not across 30 callers.

Your turn: silence the compiler

Here's a real TypeScript compiler, loaded in your browser. Click Check the types: it will find the error — without executing a single line. Read the error message, fix the call, and re-check until ✓ 0 errors. You can also click Run to see what the code produces once fixed.

🧐 TypeScript lab · the compiler judges your code (strict mode)
Stuck on the fix? Show it

You need at least the name — greet('Sam'). The polite parameter is optional (the ?), but name is required: without it, the compiler shows Expected 1-2 arguments, but got 0. With greet('Sam'), ✓ 0 errors and running it prints "Hi Sam!". You can also try greet('Sam', true) for the polite version.

What this changes with AI

A typed signature is a permanent prompt. When the AI sees filter(scores: number[], keep: (n: number) => boolean): number[] in context, it knows exactly what callback to generate — no need to explain it in natural language. And if it generates a callback that returns a string instead of a boolean, the compiler underlines it in red before you run anything.

The other benefit: the compiler counts arguments for you. If the AI generates a call with a forgotten argument — like our sendEmail at the start — the error appears immediately in the editor. No more empty emails reaching the client.

Type your public functions first — return type included. It's the best contract-to-effort ratio in the language: a few extra characters, and every call throughout the project is verified automatically. The AI too.

La fonction comme prise électrique : deux fiches d'entrée (nom: string obligatoire, poli?: boolean optionnel) et une sortie (: string). En dessous, un appel conforme saluer('Sam') en vert et un appel sans arguments en rouge avec le message Expected 1-2 arguments, but got 0. saluer( ) nom: string poli?: boolean (optionnel) retour : string saluer('Sam') nom fourni, poli omis → OK saluer() Expected 1-2 arguments, but got 0
La fonction = une prise électrique : le compilateur contrôle que chaque connecteur correspond à la bonne fiche.
The function as an electrical socket: two input prongs (name: string required, polite?: boolean optional) and one output (: string). Below, a valid call greet('Sam') in green and a call with no arguments in red with the message Expected 1-2 arguments, but got 0. greet( ) name: string polite?: boolean (optional) returns : string greet('Sam') name provided, polite omitted → OK greet() Expected 1-2 arguments, but got 0
The function = an electrical socket: the compiler checks that every connector matches the right prong.

🎯 Pratique

S'entraîner (clique pour ouvrir) :

💬 Ré-explique sans regarder
Ré-explique sans regarder

Décris tout ce que fixe la signature filtrer(notes: number[], garder: (n: number) => boolean): number[].

Une bonne explication dit : types et nombre des paramètres (un tableau de numbers, puis une fonction) ; forme exacte du callback (prend un number, renvoie un boolean, pas n'importe quelle fonction) ; type de retour (un tableau de numbers). Tout appel et toute implémentation sont vérifiés contre ça par le compilateur.
🧠 Rappel libre
Rappel libre

Sans remonter : quelle différence entre poli?: boolean et poli = false ?

poli?: boolean : l'argument peut être absent ; à l'intérieur, le type est boolean | undefined, il faut gérer le cas. poli = false : jamais undefined à l'intérieur (valeur par défaut), TypeScript infère boolean. Les deux rendent l'argument facultatif à l'appel, mais le comportement interne diffère.
⚖️ Juge le code de l'IA
Accepter ou rejeter le code de l'IA

Le compilateur signale Expected 2 arguments, but got 1 sur un appel. Tu demandes à l'IA de corriger. Elle répond : « Corrigé ! J'ai rendu tous les paramètres optionnels (destinataire?, sujet?, corps?), l'erreur a disparu. » Tu acceptes, ou tu rejettes ?

À rejeter. L'erreur disait qu'un appel oubliait un argument requis. Rendre tout optionnel ne corrige pas l'appel : ça cache le bug et autorise désormais des emails sans destinataire, sans sujet, sans corps. Le contrat est affaibli, pas réparé. Le bon réflexe : corriger l'appel qui manquait l'argument, pas la signature qui le réclamait.
Que vérifie TypeScript à chaque APPEL de fonction ?
Où doit se placer un paramètre optionnel « ? » ?
Comment typer un callback « prend un number, renvoie un boolean » ?
Pourquoi écrire explicitement le type de RETOUR des fonctions publiques, alors que TS l'infère ?
Prochaine étape

Tu maîtrises maintenant le contrat complet d'une fonction. Leçon 6 : les génériques : le « moule à gâteau » du TypeScript. Du code réutilisable qui garde le contrat sans répéter les types. La leçon pivot, en douceur.

Leçon 6 : Les génériques →
Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement