Funzioni Rest Parameters

Edoardo Midali
Edoardo Midali

I rest parameters in TypeScript permettono a una funzione di accettare un numero indefinito di argomenti, raccolti automaticamente in un array. Utilizzando la sintassi ... prima del nome del parametro, si crea un’API flessibile che può gestire da zero a molti argomenti senza dover definire un numero fisso di parametri.

Fondamenti e Sintassi

Un rest parameter si dichiara con tre punti ... seguiti dal nome del parametro e dal tipo array. Raccoglie tutti gli argomenti rimanenti in un array, che può essere vuoto se nessun argomento viene passato.

// Rest parameter di numeri
function somma(...numeri: number[]): number {
  return numeri.reduce((acc, n) => acc + n, 0);
}

console.log(somma()); // 0
console.log(somma(1, 2, 3)); // 6
console.log(somma(10, 20, 30, 40, 50)); // 150

// Rest parameter di stringhe
function concatena(...parole: string[]): string {
  return parole.join(" ");
}

console.log(concatena("Hello", "World")); // "Hello World"
console.log(concatena("TypeScript", "è", "fantastico")); // "TypeScript è fantastico"

Tipizzazione Rest Parameters

I rest parameters devono essere tipizzati come array o tuple. TypeScript inferisce il tipo degli elementi dall’annotazione fornita.

// Tipo esplicito
function massimo(...valori: number[]): number {
  return Math.max(...valori);
}

// Con tipi complessi
interface Prodotto {
  nome: string;
  prezzo: number;
}

function calcolaTotale(...prodotti: Prodotto[]): number {
  return prodotti.reduce((sum, p) => sum + p.prezzo, 0);
}

const totale = calcolaTotale(
  { nome: "A", prezzo: 10 },
  { nome: "B", prezzo: 20 },
  { nome: "C", prezzo: 15 }
);

// Rest parameter con union types
function processa(...items: (string | number)[]): void {
  items.forEach((item) => {
    if (typeof item === "string") {
      console.log(item.toUpperCase());
    } else {
      console.log(item * 2);
    }
  });
}

processa("hello", 5, "world", 10);

Posizionamento Rest Parameters

Il rest parameter deve essere l’ultimo parametro della funzione. Non è possibile avere parametri dopo un rest parameter.

// Corretto: rest parameter alla fine
function creaMessaggio(
  prefisso: string,
  suffisso: string,
  ...parole: string[]
): string {
  return `${prefisso} ${parole.join(" ")} ${suffisso}`;
}

console.log(creaMessaggio("[", "]", "a", "b", "c")); // "[ a b c ]"

// ERRORE: rest parameter non può essere seguito da altri parametri
// function errato(...items: string[], ultimo: string): void {}

Rest Parameters con Parametri Normali

Combinare parametri normali con rest parameters per richiedere un minimo di argomenti mantenendo flessibilità.

// Almeno un argomento richiesto
function media(primo: number, ...altri: number[]): number {
  const tutti = [primo, ...altri];
  return tutti.reduce((sum, n) => sum + n, 0) / tutti.length;
}

console.log(media(10)); // 10
console.log(media(10, 20, 30)); // 20

// Più parametri obbligatori prima di rest
function log(livello: string, timestamp: Date, ...messaggi: string[]): void {
  console.log(`[${livello}] [${timestamp.toISOString()}]`, ...messaggi);
}

log("ERROR", new Date(), "Connessione", "fallita");

Rest Parameters con Tuple Types

Usare tuple types per rest parameters quando si conosce la struttura precisa degli argomenti variabili.

// Tuple type per rest parameter
function creaCoordinate(...punti: [number, number][]): string {
  return punti.map(([x, y]) => `(${x},${y})`).join(" - ");
}

console.log(creaCoordinate([0, 0], [10, 20], [30, 40]));
// "(0,0) - (10,20) - (30,40)"

// Tuple mista
function configurazioni(
  ...configs: [string, number | boolean][]
): Record<string, number | boolean> {
  const obj: Record<string, number | boolean> = {};
  configs.forEach(([chiave, valore]) => {
    obj[chiave] = valore;
  });
  return obj;
}

const config = configurazioni(
  ["porta", 3000],
  ["debug", true],
  ["timeout", 5000]
);

Spread Operator con Rest Parameters

L’operatore spread ... può essere usato per passare array come argomenti individuali a funzioni con rest parameters.

function moltiplica(...numeri: number[]): number {
  return numeri.reduce((acc, n) => acc * n, 1);
}

const valori = [2, 3, 4];
console.log(moltiplica(...valori)); // 24

// Combinare spread con argomenti normali
function combina(separatore: string, ...parti: string[]): string {
  return parti.join(separatore);
}

const parole = ["uno", "due", "tre"];
console.log(combina("-", ...parole)); // "uno-due-tre"
console.log(combina(":", "a", "b", ...parole)); // "a:b:uno:due:tre"

Rest Parameters in Arrow Functions

Le arrow function supportano rest parameters con la stessa sintassi delle function tradizionali.

// Arrow function con rest parameter
const sommaQuadrati = (...numeri: number[]): number => {
  return numeri.reduce((sum, n) => sum + n * n, 0);
};

console.log(sommaQuadrati(1, 2, 3)); // 14 (1 + 4 + 9)

// Arrow concisa
const concatena = (...strs: string[]): string => strs.join("");

// Con destructuring
const descrivi = (nome: string, ...attributi: [string, any][]): string => {
  const props = attributi.map(([k, v]) => `${k}: ${v}`).join(", ");
  return `${nome} { ${props} }`;
};

console.log(descrivi("Utente", ["id", 1], ["nome", "Mario"], ["attivo", true]));

Rest Parameters in Metodi di Classe

I metodi di classe possono utilizzare rest parameters per API flessibili su istanze.

class Logger {
  private livello: string;

  constructor(livello: string = "INFO") {
    this.livello = livello;
  }

  log(...messaggi: string[]): void {
    console.log(`[${this.livello}]`, ...messaggi);
  }

  formatta(template: string, ...valori: any[]): string {
    let risultato = template;
    valori.forEach((valore, i) => {
      risultato = risultato.replace(`{${i}}`, String(valore));
    });
    return risultato;
  }
}

const logger = new Logger("DEBUG");
logger.log("Sistema", "avviato", "correttamente");
console.log(logger.formatta("Utente {0} ha {1} anni", "Mario", 30));

Generics con Rest Parameters

Combinare generics con rest parameters per funzioni type-safe che operano su tipi diversi.

// Generic rest parameter
function primo<T>(...items: T[]): T | undefined {
  return items[0];
}

const num = primo(1, 2, 3); // number | undefined
const str = primo("a", "b"); // string | undefined

// Multiple generics
function zip<T, U>(...arrays: [T[], U[]]): Array<[T, U]> {
  const [arr1, arr2] = arrays;
  const length = Math.min(arr1.length, arr2.length);
  const result: Array<[T, U]> = [];

  for (let i = 0; i < length; i++) {
    result.push([arr1[i], arr2[i]]);
  }

  return result;
}

const zipped = zip([1, 2, 3], ["a", "b", "c"]);
// Array<[number, string]>

Pattern Common con Rest Parameters

Rest parameters facilitano diversi pattern comuni nella programmazione funzionale e utility functions.

// Compose functions
function componi<T>(...funzioni: Array<(x: T) => T>): (x: T) => T {
  return (valore: T) => {
    return funzioni.reduceRight((acc, fn) => fn(acc), valore);
  };
}

const trasforma = componi(
  (n: number) => n + 1,
  (n: number) => n * 2,
  (n: number) => n ** 2
);

console.log(trasforma(3)); // 19 ((3^2 * 2) + 1)

// Merge objects
function merge<T extends object>(...oggetti: T[]): T {
  return Object.assign({}, ...oggetti);
}

const config = merge({ porta: 3000 }, { host: "localhost" }, { debug: true });

// Filter undefined
function compatta<T>(...valori: (T | undefined)[]): T[] {
  return valori.filter((v): v is T => v !== undefined);
}

const risultato = compatta(1, undefined, 2, undefined, 3);
// [1, 2, 3]

Rest Parameters con Overloading

Combinare rest parameters con function overloading per tipizzazione precisa basata sul numero di argomenti.

// Overload con rest parameters
function crea(nome: string): { nome: string };
function crea(nome: string, eta: number): { nome: string; eta: number };
function crea(...args: [string] | [string, number]): any {
  if (args.length === 1) {
    return { nome: args[0] };
  }
  return { nome: args[0], eta: args[1] };
}

const obj1 = crea("Mario"); // { nome: string }
const obj2 = crea("Anna", 25); // { nome: string; eta: number }

Rest Parameters e Arguments Object

I rest parameters sostituiscono l’oggetto arguments legacy, fornendo array tipizzato e compatibilità con arrow functions.

// Vecchio stile con arguments (non tipizzato)
function vecchioStile() {
  // arguments non è un array vero
  const arr = Array.from(arguments);
  console.log(arr);
}

// Moderno con rest parameters (tipizzato)
function moderno(...args: any[]) {
  // args è già un array
  console.log(args);
  args.forEach((arg) => console.log(arg));
}

// Rest parameters funzionano con arrow functions
const arrowFn = (...items: number[]) => {
  return items.reduce((sum, n) => sum + n, 0);
};

Validazione e Type Guards con Rest Parameters

Implementare validazione e type narrowing su rest parameters per garantire correttezza dei dati.

// Type guard su rest parameter
function tuttiNumeri(...valori: any[]): valori is number[] {
  return valori.every((v) => typeof v === "number");
}

function sommaValidata(...valori: any[]): number {
  if (!tuttiNumeri(...valori)) {
    throw new Error("Tutti gli argomenti devono essere numeri");
  }
  return valori.reduce((sum, n) => sum + n, 0);
}

// Validazione con generic
function filtraPerTipo<T>(tipo: string, ...items: any[]): T[] {
  return items.filter((item) => typeof item === tipo) as T[];
}

const numeri = filtraPerTipo<number>("number", 1, "a", 2, true, 3);
// [1, 2, 3]

Performance Considerations

Rest parameters creano un nuovo array ad ogni chiamata. Per funzioni chiamate molto frequentemente in scenari performance-critical, considerare alternative se appropriato.

// Rest parameter: crea array
function sommaRest(...numeri: number[]): number {
  return numeri.reduce((sum, n) => sum + n, 0);
}

// Alternativa: array diretto (no overhead creazione)
function sommaArray(numeri: number[]): number {
  return numeri.reduce((sum, n) => sum + n, 0);
}

// Per uso normale, rest parameters sono preferibili per ergonomia
sommaRest(1, 2, 3, 4, 5); // Più naturale
sommaArray([1, 2, 3, 4, 5]); // Richiede array letterale

Conclusioni

I rest parameters in TypeScript forniscono meccanismo elegante e type-safe per funzioni con numero variabile di argomenti. Sostituiscono completamente l’oggetto arguments legacy con sintassi moderna che produce array tipizzati, funziona con arrow functions, e si integra perfettamente con features TypeScript come generics e overloading. La flessibilità dei rest parameters li rende ideali per utility functions, API variadic, e pattern funzionali dove il numero di argomenti non è predeterminato, migliorando ergonomia senza sacrificare type safety.