Se una lingua supporta l'astrazione di controllo, ciò significa che possiamo definire i nostri costrutti di flusso di controllo. Mentre questo può essere sicuramente abusato e rendere un programma totalmente incomprensibile, può anche rendere un programma molto più chiaro.
Le precondizioni per l'astrazione del controllo sono funzioni di ordine superiore, chiusure e lambda, sebbene molte astrazioni siano sconvenienti senza continuazioni di prima classe. Si noti che gli oggetti sono in qualche modo equivalenti alle chiusure, quindi è possibile utilizzare alcuni aspetti dell'astrazione del flusso di controllo anche nei linguaggi non funzionali, sebbene in genere con una sintassi più confusa.
Ad esempio, consideriamo un blocco using
in C # (o try-with-resource in Java o with
in Python). Qui, le lingue hanno implementato una sintassi speciale per rappresentare questo flusso di controllo:
- una risorsa viene acquisita
- viene eseguito un blocco
- quando il controllo lascia il blocco, la risorsa viene chiusa.
Ma con funzioni di ordine superiore, potremmo averlo implementato noi stessi!
static void using<T>(T resource, Action<T> body)
where T: IDisposable {
try {
body(resource);
} finally {
if (resource != null)
resource.Close();
}
}
Potrebbe quindi essere usato come:
using(new File("foo.txt"), file => {
...
});
Quindi le astrazioni di controllo ci consentono di evolvere il linguaggio con costrutti di controllo definiti dall'utente, proprio come l'astrazione dei dati ci consente di creare strutture di dati definite dall'utente.
Un altro buon esempio è un operatore a cascata di metodi, che ci consente di eseguire alcuni effetti collaterali in un metodo di chiamata:
static T Tap<T>(this T instance, Action<T> body) {
body(instance);
return instance;
}
Sembra inutile, ma a volte può essere abbastanza comodo configurare gli oggetti in un'interfaccia fluente, senza doverli estrarre in una funzione variabile o separata.
return new Foo() // where Foo is a reference type
.Tap(f => f.X = 7)
.Tap(f => f.Y = 18)
.Tap(f => f.Name = "Foo Bar");
Ci sono alcuni usi degni di nota dell'astrazione di controllo:
-
Promesse in JavaScript (e attività / futures in C #) possono essere viste come un'astrazione di controllo che consente di strutturare il codice asincrono senza callback profondamente nidificati. Tuttavia, senza continuazioni di prima classe, abbiamo ancora bisogno di callback per le promesse. Con le continuazioni, async/await
potrebbe essere implementato come una libreria.
-
I DSL incorporati traggono notevoli vantaggi dall'astrazione del flusso di controllo. Per esempio. la sintassi flessibile di Scala o Ruby si presta molto bene a tali usi. Ad esempio, una libreria parser combinator consente di assemblare un parser nel codice sorgente, ma interpreta questo parser utilizzando il proprio flusso di controllo quando viene analizzato un documento di input.
-
Monadi e Functori in Haskell e Scala possono essere interpretati sia come astrazione dei dati che come astrazione di controllo. Una semplice monade è Forse o Facoltativa (simile a Nullable in C #). Questo essenzialmente ci dà un operatore di navigazione sicura (come ?.
in C #), tranne che può essere implementato come una libreria, senza dover attendere che la lingua principale cambi. In C #, IEnumerable è un po 'simile al functor con il metodo Select
.
Al giorno d'oggi, la maggior parte delle lingue supporta un qualche tipo di astrazione del flusso di controllo perché consente agli utenti di creare grandi librerie ed essere produttivi, senza dover attendere che la lingua aggiunga un operatore mancante. Ma ci sono alcune riserve:
-
C non supporta alcuna astrazione di controllo oltre le macro. In teoria puoi implementare chiusure o oggetti con i puntatori di funzione, ma non è esattamente una sintassi conveniente.
-
Go supporta pochissime opportunità per l'astrazione del controllo. Tecnicamente ha chiusure, ma non ha generici che sono necessari per le astrazioni riutilizzabili . Solo i tipi built-in sono generici, quindi puoi ricreare alcune astrazioni di controllo usando canali e for-loops.