Funzioni Anonime

Edoardo Midali
Edoardo Midali

Le funzioni anonime in TypeScript sono funzioni dichiarate senza un identificatore esplicito, utilizzate principalmente come valori da assegnare a variabili, passare come argomenti ad altre funzioni, o restituire come risultati. Rappresentano un pattern fondamentale nella programmazione funzionale e nella gestione di callback, permettendo definizione inline di comportamenti senza inquinare lo scope con nomi di funzione.

Fondamenti e Sintassi

Una funzione anonima si dichiara usando la keyword function senza nome, oppure con la sintassi arrow function. Può essere assegnata a una variabile o usata direttamente come espressione.

// Funzione anonima tradizionale assegnata a variabile
const somma = function (a: number, b: number): number {
  return a + b;
};

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

// Arrow function concisa
const quadrato = (n: number): number => n * n;

console.log(somma(3, 4)); // 7
console.log(quadrato(5)); // 25

Inferenza di Tipo

TypeScript inferisce automaticamente i tipi di ritorno delle funzioni anonime quando possibile, riducendo verbosità mantenendo type safety. I tipi dei parametri possono essere inferiti dal contesto quando la funzione è usata come callback.

// Tipo di ritorno inferito
const doppio = (n: number) => n * 2; // number inferito

// Tipi parametri inferiti da contesto
const numeri = [1, 2, 3, 4];
const pari = numeri.filter((n) => n % 2 === 0); // n inferito come number

// Inferenza con metodi array
const nomi = ["Anna", "Marco", "Lucia"];
const maiuscole = nomi.map((nome) => nome.toUpperCase()); // nome inferito come string

Callback e Higher-Order Functions

Le funzioni anonime eccellono come callback, passate ad altre funzioni che le invocano in momenti specifici. Questo pattern è ubiquo in operazioni su array, gestione eventi, e programmazione asincrona.

// Callback in operazioni array
const prezzi = [10, 25, 15, 30];

const scontati = prezzi.map((prezzo) => prezzo * 0.9);

const costosi = prezzi.filter((prezzo) => prezzo > 20);

const totale = prezzi.reduce((acc, prezzo) => acc + prezzo, 0);

// Callback in funzioni custom
function processaArray<T>(array: T[], trasformazione: (elemento: T) => T): T[] {
  return array.map(trasformazione);
}

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

IIFE (Immediately Invoked Function Expression)

Le IIFE sono funzioni anonime invocate immediatamente dopo la definizione, creando uno scope isolato per variabili temporanee o inizializzazione.

// IIFE tradizionale
(function () {
  const temp = "scope isolato";
  console.log(temp);
})();

// IIFE con arrow function
(() => {
  const configurazione = caricaConfig();
  inizializza(configurazione);
})();

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

// IIFE asincrona
(async () => {
  const dati = await fetchDati();
  processaDati(dati);
})();

Closure e Scope

Le funzioni anonime creano closure, catturando variabili dall’ambiente circostante. Questo permette di mantenere stato privato e creare factory di funzioni.

function creaContatore() {
  let conteggio = 0;

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

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

// Closure con arrow function
function moltiplicatore(fattore: number) {
  return (n: number) => n * fattore;
}

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

Differenza tra Function e Arrow Function

Le arrow function differiscono dalle function tradizionali principalmente nel binding di this. Le arrow catturano this lessicalmente dal contesto circostante, mentre function tradizionali hanno this dinamico.

class Contatore {
  private valore = 0;

  // Arrow function: this è quello della classe
  incrementa = () => {
    this.valore++;
  };

  // Function tradizionale: this dipende da come viene chiamata
  incrementaTradizonale = function () {
    this.valore++; // this potrebbe non essere il Contatore
  };
}

const cont = new Contatore();
const metodo = cont.incrementa;
metodo(); // Funziona: this è sempre Contatore

const metodoTrad = cont.incrementaTradizonale;
metodoTrad(); // Errore: this è undefined in strict mode

Funzioni Anonime come Proprietà

Oggetti possono contenere funzioni anonime come proprietà, definendo comportamenti incapsulati.

const calcolatrice = {
  somma: (a: number, b: number) => a + b,
  sottrai: (a: number, b: number) => a - b,
  moltiplica: function (a: number, b: number): number {
    return a * b;
  },
};

console.log(calcolatrice.somma(5, 3)); // 8

// Oggetto con metodi generici
const utilita = {
  filtra: <T>(array: T[], condizione: (item: T) => boolean) => {
    return array.filter(condizione);
  },
  mappa: <T, U>(array: T[], trasforma: (item: T) => U) => {
    return array.map(trasforma);
  },
};

Funzioni che Restituiscono Funzioni

Le funzioni anonime possono essere restituite da altre funzioni, permettendo creazione dinamica di comportamenti configurabili.

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

const validaEta = creaValidatore(18, 65);
console.log(validaEta(25)); // true
console.log(validaEta(70)); // false

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

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

console.log(trasforma(3)); // 11 (3 * 2 + 5)

Event Handlers

Le funzioni anonime sono ideali per gestori di eventi, definendo comportamento inline senza necessità di funzioni nominate separate.

// DOM event handlers
button.addEventListener("click", (event: MouseEvent) => {
  console.log("Cliccato:", event.clientX, event.clientY);
});

// Rimozione event listener (richiede riferimento)
const handler = (e: Event) => console.log("Evento:", e.type);
element.addEventListener("mouseover", handler);
element.removeEventListener("mouseover", handler);

// Event emitters personalizzati
class EventEmitter {
  private handlers = new Map<string, Function[]>();

  on(evento: string, callback: Function) {
    if (!this.handlers.has(evento)) {
      this.handlers.set(evento, []);
    }
    this.handlers.get(evento)!.push(callback);
  }

  emit(evento: string, ...args: any[]) {
    const callbacks = this.handlers.get(evento);
    if (callbacks) {
      callbacks.forEach((cb) => cb(...args));
    }
  }
}

const emitter = new EventEmitter();
emitter.on("dati", (dati: any) => console.log("Ricevuto:", dati));

Funzioni Anonime Asincrone

Le funzioni anonime possono essere asincrone, permettendo uso di await e gestione di Promise inline.

// Async arrow function
const fetchUtente = async (id: string) => {
  const response = await fetch(`/api/utenti/${id}`);
  return await response.json();
};

// Callback asincrona
const ids = [1, 2, 3];
const promesse = ids.map(async (id) => {
  return await fetchDati(id);
});

// IIFE asincrona per esecuzione immediata
(async () => {
  const risultato = await operazioneAsincrona();
  console.log(risultato);
})();

// Array method con async
async function processaTutti(items: string[]) {
  for (const item of items) {
    await processaItem(item);
  }
}

Currying e Partial Application

Le funzioni anonime facilitano currying e applicazione parziale, trasformando funzioni multi-parametro in sequenze di funzioni unarie.

// Currying manuale
const sommaCarried = (a: number) => (b: number) => a + b;

const aggiungi10 = sommaCarried(10);
console.log(aggiungi10(5)); // 15

// Partial application
function parziale<T, U, V>(fn: (a: T, b: U) => V, primoArg: T): (b: U) => V {
  return (secondoArg: U) => fn(primoArg, secondoArg);
}

const moltiplica = (a: number, b: number) => a * b;
const doppio = parziale(moltiplica, 2);
console.log(doppio(5)); // 10

Type Annotations Esplicite

Per funzioni anonime complesse, annotazioni di tipo esplicite migliorano leggibilità e garantiscono contratti chiari.

// Tipo funzione completo
const elabora: (input: string) => { risultato: string; errori: string[] } = (
  input
) => {
  return {
    risultato: input.toUpperCase(),
    errori: [],
  };
};

// Type alias per riutilizzo
type Validatore = (valore: any) => { valido: boolean; messaggio?: string };

const validaEmail: Validatore = (valore) => {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return {
    valido: regex.test(valore),
    messaggio: regex.test(valore) ? undefined : "Email non valida",
  };
};

Array di Funzioni

Arrays possono contenere funzioni anonime, permettendo iterazione su comportamenti o pattern command.

const operazioni: Array<(n: number) => number> = [
  (n) => n + 1,
  (n) => n * 2,
  (n) => n ** 2,
];

let valore = 5;
operazioni.forEach((op) => {
  valore = op(valore);
});
console.log(valore); // ((5 + 1) * 2) ** 2 = 144

// Pipeline di trasformazioni
function pipeline<T>(...funzioni: Array<(x: T) => T>) {
  return (valore: T) => {
    return funzioni.reduce((acc, fn) => fn(acc), valore);
  };
}

const trasforma = pipeline(
  (s: string) => s.trim(),
  (s: string) => s.toLowerCase(),
  (s: string) => s.replace(/\s+/g, "-")
);

console.log(trasforma("  Hello World  ")); // "hello-world"

Conclusioni

Le funzioni anonime in TypeScript rappresentano uno strumento versatile per programmazione funzionale, gestione callback, e creazione di closure. La sintassi arrow function moderna offre concisione e binding lessicale di this, mentre function tradizionali mantengono rilevanza per casi specifici. L’uso appropriato di funzioni anonime migliora espressività del codice, permettendo definizione inline di comportamenti senza proliferazione di funzioni nominate, risultando essenziali per pattern come higher-order functions, event handling, e composizione funzionale.