Embedding vs. Extending

Lo ripeto spesso: ogni applicazione ha il suo linguaggio. Non esiste un linguaggio universale che sia il migliore in ogni occasione e la regola discriminante è semplice: più un linguaggio aumenta di astrazione più aumenta l’overhead di tempo e memoria per la sua esecuzione. In pratica barattiamo prestazioni in cambio di codice più scollegato dalla macchina, più facile da usare, più dinamico. Sta al programmatore soppesare i requisiti dell’applicazione e scegliere il giusto livello di prestazioni-astrazione necessario (che possiamo chiamare, coniando un’orrenda nuova parola, livello ottimale di prestastrazione).

In realtà c’è anche una terza possibilità, molto usata nella pratica, che permette allo sviluppatore di bypassare la “coperta corta” dell’indice prestastrazionale e di unire le prestazioni dei linguaggi di basso livello alla dinamicità dei linguaggi di alto livello. Questa via consiste proprio nell’accoppiare in un unico programma due linguaggi, solitamente uno di basso livello come C/C++ e un linguaggio di scripting quale Python, Lua, Ruby e così via. Ai fini di questo articolo assumeremo che ci il linguaggio di alto livello si chiami A e quello di basso livello B.

A questo punto sorge il dilemma: come mescolare questi due linguaggi? Ci sono fondamentalmente due modi diversi di combinare due linguaggi in un’applicazione: si può estendere (extending) A con moduli scritti in B, oppure si può includere (embedding) A all’interno di un programma scritto in B.

Embedding

Per embedding si intende l’inclusione dell’interprete del linguaggio di scripting all’interno del linguaggio di basso livello. Questo significa che possiamo invocare l’esecuzione di script e funzioni scritti nel linguaggio A direttamente da B. Tali script solitamente invocano callback specifiche dell’applicazione nativa permettendo quindi di “scriptare” parti dell’applicazione che sarebbe più complesso scrivere direttamente in B.

Questo è l’approccio utilizzato da applicazioni quali Blender. Blender, scritto in C++, integra una shell Python grazie alla quale è possibile eseguire script che interagiscono direttamente con l’applicazione stessa.

Extending

Per estendere un linguaggio si intende l’aggiunta di moduli (o librerie) scritti in B ad un programma scritto in A. Dal punto di vista di A l’estensione è del tutto trasparente e si comporta come la semplice aggiunta di moduli nativi: A importa il modulo esteso scritto in B tramite del codice di interfaccia che ha il solo scopo di convertire le chiamate di A in chiamate di basso livello per B.

Questo approccio, chiamato anche wrapping o binding, è noto soprattutto per le librerie. Ad esempio PyGtk è composto quasi esclusivamente dai moduli di interfaccia che si occupano di chiamare la libreria vera e propria scritta in C.

Estendere o Includere?

A questo punto la domanda sorge spontanea: estendo o includo? Se sto scrivendo un programma ed ho intenzione di mescolare linguaggi di alto e basso livello quale approccio scelgo? La domanda è molto interessante ed è un dubbio che assale molti sviluppatori.

La risposta tuttavia non è del tutto ovvia e dipende da diversi fattori, sia tecnici che estetici che filosofici. In pratica una risposta certa non esiste ma posso comunque darvi alcune mie opinioni.

La critica principale all’embedding è che l’inclusione presuppone un’estensione. A meno che non stiate includendo un interprete A in B col solo scopo di eseguire vanamente codice dovete comunque esporre delle API per B e includerle nel namespace di A in modo che esso possa interagire con B. In pratica da B chiamate script in A che devono comunicare con B. Questo “feedback” va comunque gestito con una sorta di interfaccia ad hoc che può essere visto come un’estensione di A.

È quello che succede con Blender: quando eseguite il codice Python dovete importare il modulo bpy il quale altro non è che un modulo di estensione per l’interprete interno di Python. Non c’è modo di importare bpy al di fuori dell’interprete di Blender poiché è specifico della versione embedded di Python usata nel software.

La domanda sorge spontanea: perché includere per poi estendere quando potrei estendere e basta?

Il secondo punto è che con il tempo vi troverete automaticamente ad estendere A. Man mano che ci prendete mano e gusto vi troverete a strutturare il codice B in tante piccole funzioni che gestirete quasi interamente tramite script A. Comincerete a spostare sempre più funzionalità in A e alla fine vi ritroverete con una estensione di A. A questo punto tanto vale partire subito con l’idea di estendere A.

E quindi?

E quindi come al solito sta allo sviluppatore valutare il caso specifico. La mia idea è che, in generale, l’inclusione richieda più lavoro dell’estensione. Ci sono però linguaggi nati per fare questo mestiere come il Lua, in questi casi l’embedding è decisamente intuitivo e la comunicazione bidirezionale fra i due linguaggi è quasi intuitiva. In questi casi un approccio misto (fake-embedding) risulta una scelta vincente in molti casi.

Quindi il mio consiglio è di puntate soprattutto ad estendere (progettate quindi le vostre API come se andassero ad estendere un linguaggio esterno) ma tenete presente ci possono essere casi in cui l’inclusione è preferibile. Valutate molto attentamente questa possibilità prima di mettervi a lavoro attivamente.

Comments are closed.