Funzioni

Edoardo Midali
Edoardo Midali

Le funzioni in TypeScript rappresentano blocchi di codice riutilizzabili che eseguono operazioni specifiche, accettando input attraverso parametri e restituendo output. TypeScript estende le funzioni JavaScript con tipizzazione statica, permettendo di specificare tipi per parametri e valori di ritorno, garantendo type safety e migliorando documentazione e manutenibilità del codice.

Dichiarazione di Funzioni

Le funzioni si possono dichiarare in diversi modi: dichiarazione tradizionale, espressione funzione, o arrow function.

// Dichiarazione tradizionale
function somma(a: number, b: number): number {
  return a + b;
}

// Espressione funzione
const sottrai = function (a: number, b: number): number {
  return a - b;
};

// Arrow function
const moltiplica = (a: number, b: number): number => {
  return a * b;
};

// Arrow function concisa
const dividi = (a: number, b: number): number => a / b;

console.log(somma(5, 3)); // 8
console.log(sottrai(5, 3)); // 2
console.log(moltiplica(5, 3)); // 15
console.log(dividi(6, 3)); // 2

Tipizzazione Parametri e Return

TypeScript richiede tipizzazione esplicita dei parametri, mentre il tipo di ritorno può essere inferito o specificato esplicitamente.

// Tipi espliciti per parametri e return
function saluta(nome: string): string {
  return `Ciao, ${nome}!`;
}

// Return type inferito
function doppio(n: number) {
  return n * 2; // number inferito
}

// Multipli parametri con tipi diversi
function creaUtente(
  nome: string,
  eta: number,
  attivo: boolean
): { nome: string; eta: number; attivo: boolean } {
  return { nome, eta, attivo };
}

// Funzione void (nessun return)
function log(messaggio: string): void {
  console.log(messaggio);
}

Parametri Opzionali e Default

I parametri possono essere resi opzionali con ? o avere valori default.

// Parametri opzionali
function salutaPersona(nome: string, cognome?: string): string {
  if (cognome) {
    return `Ciao, ${nome} ${cognome}`;
  }
  return `Ciao, ${nome}`;
}

salutaPersona("Mario");
salutaPersona("Mario", "Rossi");

// Parametri con valori default
function configuraServer(
  porta: number = 3000,
  host: string = "localhost"
): void {
  console.log(`Server su ${host}:${porta}`);
}

configuraServer(); // usa default
configuraServer(8080); // custom porta
configuraServer(8080, "0.0.0.0"); // custom entrambi

// Combinazione opzionali e default
function creaConfig(
  nome: string,
  debug?: boolean,
  timeout: number = 5000
): object {
  return { nome, debug, timeout };
}

Rest Parameters

I rest parameters raccolgono numero variabile di argomenti in un array.

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

console.log(sommaMultipla(1, 2, 3)); // 6
console.log(sommaMultipla(10, 20, 30, 40)); // 100

// Rest parameter dopo parametri normali
function formattaMessaggio(prefisso: string, ...parole: string[]): string {
  return `${prefisso}: ${parole.join(" ")}`;
}

console.log(formattaMessaggio("INFO", "Server", "avviato"));

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

Function Types

Definire tipi per funzioni permette di tipizzare variabili, parametri e return types che sono funzioni.

// Type alias per funzione
type Operazione = (a: number, b: number) => number;

const somma: Operazione = (a, b) => a + b;
const sottrazione: Operazione = (a, b) => a - b;

// Interface per funzione
interface Validatore {
  (valore: string): boolean;
}

const nonVuoto: Validatore = (valore) => valore.length > 0;
const isEmail: Validatore = (valore) => valore.includes("@");

// Funzione come parametro
function applica(valore: number, operazione: (n: number) => number): number {
  return operazione(valore);
}

console.log(applica(5, (n) => n * 2)); // 10
console.log(applica(5, (n) => n + 10)); // 15

// Funzione che restituisce funzione
function creaMultiplicatore(fattore: number): (n: number) => number {
  return (n: number) => n * fattore;
}

const triplo = creaMultiplicatore(3);
console.log(triplo(4)); // 12

Overloading

L’overloading permette di dichiarare multiple firme per una funzione.

// Firme overload
function converti(valore: string): number;
function converti(valore: number): string;

// Implementazione
function converti(valore: string | number): string | number {
  if (typeof valore === "string") {
    return parseInt(valore, 10);
  }
  return valore.toString();
}

const num = converti("123"); // number
const str = converti(456); // string

// Overload con diverso numero parametri
function crea(nome: string): { nome: string };
function crea(nome: string, eta: number): { nome: string; eta: number };

function crea(nome: string, eta?: number): any {
  if (eta !== undefined) {
    return { nome, eta };
  }
  return { nome };
}

This Parameter

TypeScript permette di tipizzare il valore di this in funzioni.

// Tipizzazione this
interface Utente {
  nome: string;
  saluta(this: Utente): void;
}

const utente: Utente = {
  nome: "Mario",
  saluta(this: Utente) {
    console.log(`Ciao, sono ${this.nome}`);
  },
};

utente.saluta(); // OK
// const fn = utente.saluta;
// fn(); // ERRORE: this mancante

// Arrow function cattura this lessicalmente
class Contatore {
  valore = 0;

  incrementa = () => {
    this.valore++;
  };
}

const cont = new Contatore();
const inc = cont.incrementa;
inc(); // OK: this sempre Contatore

Generics nelle Funzioni

I generics permettono funzioni che operano su tipi diversi mantenendo type safety.

// Generic semplice
function primo<T>(array: T[]): T | undefined {
  return array[0];
}

const n = primo([1, 2, 3]); // number | undefined
const s = primo(["a", "b"]); // string | undefined

// Multipli generics
function coppia<T, U>(a: T, b: U): [T, U] {
  return [a, b];
}

const risultato = coppia("hello", 42); // [string, number]

// Generic con constraints
function lunghezza<T extends { length: number }>(item: T): number {
  return item.length;
}

lunghezza("stringa"); // OK
lunghezza([1, 2, 3]); // OK
// lunghezza(123);    // ERRORE

// Generic con inferenza
function mappa<T, U>(array: T[], fn: (item: T) => U): U[] {
  return array.map(fn);
}

const numeri = mappa([1, 2, 3], (n) => n.toString()); // string[]

Async Functions

Le funzioni async restituiscono Promise e permettono uso di await.

// Funzione async
async function fetchDati(url: string): Promise<any> {
  const response = await fetch(url);
  return await response.json();
}

// Arrow async
const caricaUtente = async (id: string): Promise<User> => {
  const response = await fetch(`/api/utenti/${id}`);
  return await response.json();
};

// Async con gestione errori
async function operazioneSicura(): Promise<string | null> {
  try {
    const risultato = await operazioneRischiosa();
    return risultato;
  } catch (error) {
    console.error(error);
    return null;
  }
}

// Multiple await
async function processaDati(): Promise<void> {
  const dati1 = await fetchDati("/api/1");
  const dati2 = await fetchDati("/api/2");
  console.log(dati1, dati2);
}

// Await in parallelo
async function paralello(): Promise<void> {
  const [dati1, dati2] = await Promise.all([
    fetchDati("/api/1"),
    fetchDati("/api/2"),
  ]);
}

IIFE (Immediately Invoked Function Expression)

Funzioni eseguite immediatamente dopo la dichiarazione per creare scope isolato.

// IIFE tradizionale
(function () {
  const variabilePrivata = "nascosta";
  console.log(variabilePrivata);
})();

// IIFE arrow
(() => {
  const config = { porta: 3000 };
  console.log(config);
})();

// IIFE con parametri
((nome: string) => {
  console.log(`Ciao ${nome}`);
})("TypeScript");

// IIFE async
(async () => {
  const dati = await fetchDati("/api/data");
  processaDati(dati);
})();

Callback e Higher-Order Functions

Funzioni che accettano altre funzioni come parametri o le restituiscono.

// Callback
function processaArray(
  array: number[],
  callback: (n: number) => number
): number[] {
  return array.map(callback);
}

const raddoppiati = processaArray([1, 2, 3], (n) => n * 2);

// Higher-order function
function creaValidatore(min: number, max: number): (valore: number) => boolean {
  return (valore: number) => valore >= min && valore <= max;
}

const validaEta = creaValidatore(18, 100);
console.log(validaEta(25)); // true

// Composizione
function componi<T>(f: (x: T) => T, g: (x: T) => T): (x: T) => T {
  return (x: T) => f(g(x));
}

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

console.log(trasforma(5)); // 11

Closure

Le funzioni possono catturare variabili dall’ambiente circostante.

// Closure base
function creaContatore() {
  let conteggio = 0;

  return function (): number {
    return ++conteggio;
  };
}

const contatore = creaContatore();
console.log(contatore()); // 1
console.log(contatore()); // 2

// Closure con stato privato
function creaCalcolatrice() {
  let memoria = 0;

  return {
    aggiungi(n: number) {
      memoria += n;
    },
    sottrai(n: number) {
      memoria -= n;
    },
    getMemoria() {
      return memoria;
    },
  };
}

const calc = creaCalcolatrice();
calc.aggiungi(10);
calc.sottrai(3);
console.log(calc.getMemoria()); // 7

// Factory con closure
function creaFormattatore(prefisso: string) {
  return (testo: string): string => {
    return `${prefisso}: ${testo}`;
  };
}

const errore = creaFormattatore("ERRORE");
const info = creaFormattatore("INFO");

console.log(errore("Qualcosa è andato storto"));
console.log(info("Sistema avviato"));

Metodi di Funzioni

Le funzioni hanno metodi come call, apply, e bind per controllare this e parametri.

// Call
function saluta(this: { nome: string }, prefisso: string) {
  return `${prefisso}, ${this.nome}`;
}

const persona = { nome: "Mario" };
console.log(saluta.call(persona, "Ciao")); // "Ciao, Mario"

// Apply
function somma(a: number, b: number, c: number): number {
  return a + b + c;
}

const numeri = [1, 2, 3];
console.log(somma.apply(null, numeri)); // 6

// Bind
function moltiplica(this: { fattore: number }, n: number): number {
  return n * this.fattore;
}

const obj = { fattore: 10 };
const moltiplicaPer10 = moltiplica.bind(obj);
console.log(moltiplicaPer10(5)); // 50

Ricorsione

Funzioni che chiamano se stesse per risolvere problemi ricorsivi.

// Ricorsione base
function fattoriale(n: number): number {
  if (n <= 1) return 1;
  return n * fattoriale(n - 1);
}

console.log(fattoriale(5)); // 120

// Ricorsione con accumulatore (tail recursion)
function sommaDa(n: number, acc: number = 0): number {
  if (n === 0) return acc;
  return sommaDa(n - 1, acc + n);
}

console.log(sommaDa(100)); // 5050

// Ricorsione su strutture
function sommaArray(arr: number[]): number {
  if (arr.length === 0) return 0;
  return arr[0] + sommaArray(arr.slice(1));
}

console.log(sommaArray([1, 2, 3, 4])); // 10

Conclusioni

Le funzioni in TypeScript rappresentano building blocks fondamentali per organizzare logica riutilizzabile con type safety completa. La tipizzazione di parametri e return values previene errori a compile time, mentre features come generics, overloading, e async/await permettono astrazione potente mantenendo chiarezza. Arrow functions forniscono sintassi concisa con binding lessicale di this, mentre pattern come closure e higher-order functions abilitano programmazione funzionale elegante. Comprendere le diverse forme di dichiarazione e utilizzo delle funzioni risulta essenziale per scrivere codice TypeScript modulare, testabile e manutenibile.